/*!
 * UEditorPlus
 * version: 2.0.0
*/
(function(){

// editor.js
UEDITOR_CONFIG = window.UEDITOR_CONFIG || {};

var baidu = window.baidu || {};

window.baidu = baidu;

window.UE = baidu.editor = {
  plugins: {},
  commands: {},
  instants: {},
  I18N: {},
  _customizeUI: {},
  version: "3.1.0"
};
var dom = (UE.dom = {});


// core/browser.js
/**
 * 浏览器判断模块
 * @file
 * @module UE.browser
 * @since 1.2.6.1
 */

/**
 * 提供浏览器检测的模块
 * @unfile
 * @module UE.browser
 */
var browser = (UE.browser = (function() {
  var agent = navigator.userAgent.toLowerCase(),
    opera = window.opera,
    browser = {
      /**
         * @property {boolean} ie 检测当前浏览器是否为IE
         * @example
         * ```javascript
         * if ( UE.browser.ie ) {
         *     console.log( '当前浏览器是IE' );
         * }
         * ```
         */
      ie: /(msie\s|trident.*rv:)([\w.]+)/i.test(agent),

      /**
         * @property {boolean} opera 检测当前浏览器是否为Opera
         * @example
         * ```javascript
         * if ( UE.browser.opera ) {
         *     console.log( '当前浏览器是Opera' );
         * }
         * ```
         */
      opera: !!opera && opera.version,

      /**
         * @property {boolean} webkit 检测当前浏览器是否是webkit内核的浏览器
         * @example
         * ```javascript
         * if ( UE.browser.webkit ) {
         *     console.log( '当前浏览器是webkit内核浏览器' );
         * }
         * ```
         */
      webkit: agent.indexOf(" applewebkit/") > -1,

      /**
         * @property {boolean} mac 检测当前浏览器是否是运行在mac平台下
         * @example
         * ```javascript
         * if ( UE.browser.mac ) {
         *     console.log( '当前浏览器运行在mac平台下' );
         * }
         * ```
         */
      mac: agent.indexOf("macintosh") > -1,

      /**
         * @property {boolean} quirks 检测当前浏览器是否处于“怪异模式”下
         * @example
         * ```javascript
         * if ( UE.browser.quirks ) {
         *     console.log( '当前浏览器运行处于“怪异模式”' );
         * }
         * ```
         */
      quirks: document.compatMode == "BackCompat"
    };

  /**
    * @property {boolean} gecko 检测当前浏览器内核是否是gecko内核
    * @example
    * ```javascript
    * if ( UE.browser.gecko ) {
    *     console.log( '当前浏览器内核是gecko内核' );
    * }
    * ```
    */
  browser.gecko =
    navigator.product == "Gecko" &&
    !browser.webkit &&
    !browser.opera &&
    !browser.ie;

  var version = 0;

  // Internet Explorer 6.0+
  if (browser.ie) {
    var v1 = agent.match(/(?:msie\s([\w.]+))/);
    var v2 = agent.match(/(?:trident.*rv:([\w.]+))/);
    if (v1 && v2 && v1[1] && v2[1]) {
      version = Math.max(v1[1] * 1, v2[1] * 1);
    } else if (v1 && v1[1]) {
      version = v1[1] * 1;
    } else if (v2 && v2[1]) {
      version = v2[1] * 1;
    } else {
      version = 0;
    }

    browser.ie11Compat = document.documentMode == 11;
    /**
         * @property { boolean } ie9Compat 检测浏览器模式是否为 IE9 兼容模式
         * @warning 如果浏览器不是IE， 则该值为undefined
         * @example
         * ```javascript
         * if ( UE.browser.ie9Compat ) {
         *     console.log( '当前浏览器运行在IE9兼容模式下' );
         * }
         * ```
         */
    browser.ie9Compat = document.documentMode == 9;

    /**
         * @property { boolean } ie8 检测浏览器是否是IE8浏览器
         * @warning 如果浏览器不是IE， 则该值为undefined
         * @example
         * ```javascript
         * if ( UE.browser.ie8 ) {
         *     console.log( '当前浏览器是IE8浏览器' );
         * }
         * ```
         */
    browser.ie8 = !!document.documentMode;

    /**
         * @property { boolean } ie8Compat 检测浏览器模式是否为 IE8 兼容模式
         * @warning 如果浏览器不是IE， 则该值为undefined
         * @example
         * ```javascript
         * if ( UE.browser.ie8Compat ) {
         *     console.log( '当前浏览器运行在IE8兼容模式下' );
         * }
         * ```
         */
    browser.ie8Compat = document.documentMode == 8;

    /**
         * @property { boolean } ie7Compat 检测浏览器模式是否为 IE7 兼容模式
         * @warning 如果浏览器不是IE， 则该值为undefined
         * @example
         * ```javascript
         * if ( UE.browser.ie7Compat ) {
         *     console.log( '当前浏览器运行在IE7兼容模式下' );
         * }
         * ```
         */
    browser.ie7Compat =
      (version == 7 && !document.documentMode) || document.documentMode == 7;

    /**
         * @property { boolean } ie6Compat 检测浏览器模式是否为 IE6 模式 或者怪异模式
         * @warning 如果浏览器不是IE， 则该值为undefined
         * @example
         * ```javascript
         * if ( UE.browser.ie6Compat ) {
         *     console.log( '当前浏览器运行在IE6模式或者怪异模式下' );
         * }
         * ```
         */
    browser.ie6Compat = version < 7 || browser.quirks;

    browser.ie9above = version > 8;

    browser.ie9below = version < 9;

    browser.ie11above = version > 10;

    browser.ie11below = version < 11;
  }

  // Gecko.
  if (browser.gecko) {
    var geckoRelease = agent.match(/rv:([\d\.]+)/);
    if (geckoRelease) {
      geckoRelease = geckoRelease[1].split(".");
      version =
        geckoRelease[0] * 10000 +
        (geckoRelease[1] || 0) * 100 +
        (geckoRelease[2] || 0) * 1;
    }
  }

  /**
     * @property { Number } chrome 检测当前浏览器是否为Chrome, 如果是，则返回Chrome的大版本号
     * @warning 如果浏览器不是chrome， 则该值为undefined
     * @example
     * ```javascript
     * if ( UE.browser.chrome ) {
     *     console.log( '当前浏览器是Chrome' );
     * }
     * ```
     */
  if (/chrome\/(\d+\.\d)/i.test(agent)) {
    browser.chrome = +RegExp["\x241"];
  }

  /**
     * @property { Number } safari 检测当前浏览器是否为Safari, 如果是，则返回Safari的大版本号
     * @warning 如果浏览器不是safari， 则该值为undefined
     * @example
     * ```javascript
     * if ( UE.browser.safari ) {
     *     console.log( '当前浏览器是Safari' );
     * }
     * ```
     */
  if (
    /(\d+\.\d)?(?:\.\d)?\s+safari\/?(\d+\.\d+)?/i.test(agent) &&
    !/chrome/i.test(agent)
  ) {
    browser.safari = +(RegExp["\x241"] || RegExp["\x242"]);
  }

  // Opera 9.50+
  if (browser.opera) version = parseFloat(opera.version());

  // WebKit 522+ (Safari 3+)
  if (browser.webkit)
    version = parseFloat(agent.match(/ applewebkit\/(\d+)/)[1]);

  /**
     * @property { Number } version 检测当前浏览器版本号
     * @remind
     * <ul>
     *     <li>IE系列返回值为5,6,7,8,9,10等</li>
     *     <li>gecko系列会返回10900，158900等</li>
     *     <li>webkit系列会返回其build号 (如 522等)</li>
     * </ul>
     * @example
     * ```javascript
     * console.log( '当前浏览器版本号是： ' + UE.browser.version );
     * ```
     */
  browser.version = version;

  /**
     * @property { boolean } isCompatible 检测当前浏览器是否能够与UEditor良好兼容
     * @example
     * ```javascript
     * if ( UE.browser.isCompatible ) {
     *     console.log( '浏览器与UEditor能够良好兼容' );
     * }
     * ```
     */
  browser.isCompatible =
    !browser.mobile &&
    ((browser.ie && version >= 6) ||
      (browser.gecko && version >= 10801) ||
      (browser.opera && version >= 9.5) ||
      (browser.air && version >= 1) ||
      (browser.webkit && version >= 522) ||
      false);
  return browser;
})());
//快捷方式
var ie = browser.ie,
  webkit = browser.webkit,
  gecko = browser.gecko,
  opera = browser.opera;


// core/utils.js
/**
 * 工具函数包
 * @file
 * @module UE.utils
 * @since 1.2.6.1
 */

/**
 * UEditor封装使用的静态工具函数
 * @module UE.utils
 * @unfile
 */

var utils = (UE.utils = {
  /**
     * 用给定的迭代器遍历对象
     * @method each
     * @param { Object } obj 需要遍历的对象
     * @param { Function } iterator 迭代器， 该方法接受两个参数， 第一个参数是当前所处理的value， 第二个参数是当前遍历对象的key
     * @example
     * ```javascript
     * var demoObj = {
     *     key1: 1,
     *     key2: 2
     * };
     *
     * //output: key1: 1, key2: 2
     * UE.utils.each( demoObj, funciton ( value, key ) {
     *
     *     console.log( key + ":" + value );
     *
     * } );
     * ```
     */

  /**
     * 用给定的迭代器遍历数组或类数组对象
     * @method each
     * @param { Array } array 需要遍历的数组或者类数组
     * @param { Function } iterator 迭代器， 该方法接受两个参数， 第一个参数是当前所处理的value， 第二个参数是当前遍历对象的key
     * @example
     * ```javascript
     * var divs = document.getElmentByTagNames( "div" );
     *
     * //output: 0: DIV, 1: DIV ...
     * UE.utils.each( divs, funciton ( value, key ) {
     *
     *     console.log( key + ":" + value.tagName );
     *
     * } );
     * ```
     */
  each: function(obj, iterator, context) {
    if (obj == null) return;
    if (obj.length === +obj.length) {
      for (var i = 0, l = obj.length; i < l; i++) {
        if (iterator.call(context, obj[i], i, obj) === false) return false;
      }
    } else {
      for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
          if (iterator.call(context, obj[key], key, obj) === false)
            return false;
        }
      }
    }
  },

  /**
     * 以给定对象作为原型创建一个新对象
     * @method makeInstance
     * @param { Object } protoObject 该对象将作为新创建对象的原型
     * @return { Object } 新的对象， 该对象的原型是给定的protoObject对象
     * @example
     * ```javascript
     *
     * var protoObject = { sayHello: function () { console.log('Hello UEditor!'); } };
     *
     * var newObject = UE.utils.makeInstance( protoObject );
     * //output: Hello UEditor!
     * newObject.sayHello();
     * ```
     */
  makeInstance: function(obj) {
    var noop = new Function();
    noop.prototype = obj;
    obj = new noop();
    noop.prototype = null;
    return obj;
  },

  /**
     * 将source对象中的属性扩展到target对象上
     * @method extend
     * @remind 该方法将强制把source对象上的属性复制到target对象上
     * @see UE.utils.extend(Object,Object,Boolean)
     * @param { Object } target 目标对象， 新的属性将附加到该对象上
     * @param { Object } source 源对象， 该对象的属性会被附加到target对象上
     * @return { Object } 返回target对象
     * @example
     * ```javascript
     *
     * var target = { name: 'target', sex: 1 },
     *      source = { name: 'source', age: 17 };
     *
     * UE.utils.extend( target, source );
     *
     * //output: { name: 'source', sex: 1, age: 17 }
     * console.log( target );
     *
     * ```
     */

  /**
     * 将source对象中的属性扩展到target对象上， 根据指定的isKeepTarget值决定是否保留目标对象中与
     * 源对象属性名相同的属性值。
     * @method extend
     * @param { Object } target 目标对象， 新的属性将附加到该对象上
     * @param { Object } source 源对象， 该对象的属性会被附加到target对象上
     * @param { Boolean } isKeepTarget 是否保留目标对象中与源对象中属性名相同的属性
     * @return { Object } 返回target对象
     * @example
     * ```javascript
     *
     * var target = { name: 'target', sex: 1 },
     *      source = { name: 'source', age: 17 };
     *
     * UE.utils.extend( target, source, true );
     *
     * //output: { name: 'target', sex: 1, age: 17 }
     * console.log( target );
     *
     * ```
     */
  extend: function(t, s, b) {
    if (s) {
      for (var k in s) {
        if (!b || !t.hasOwnProperty(k)) {
          t[k] = s[k];
        }
      }
    }
    return t;
  },

  /**
     * 将给定的多个对象的属性复制到目标对象target上
     * @method extend2
     * @remind 该方法将强制把源对象上的属性复制到target对象上
     * @remind 该方法支持两个及以上的参数， 从第二个参数开始， 其属性都会被复制到第一个参数上。 如果遇到同名的属性，
     *          将会覆盖掉之前的值。
     * @param { Object } target 目标对象， 新的属性将附加到该对象上
     * @param { Object... } source 源对象， 支持多个对象， 该对象的属性会被附加到target对象上
     * @return { Object } 返回target对象
     * @example
     * ```javascript
     *
     * var target = {},
     *     source1 = { name: 'source', age: 17 },
     *     source2 = { title: 'dev' };
     *
     * UE.utils.extend2( target, source1, source2 );
     *
     * //output: { name: 'source', age: 17, title: 'dev' }
     * console.log( target );
     *
     * ```
     */
  extend2: function(t) {
    var a = arguments;
    for (var i = 1; i < a.length; i++) {
      var x = a[i];
      for (var k in x) {
        if (!t.hasOwnProperty(k)) {
          t[k] = x[k];
        }
      }
    }
    return t;
  },

  /**
     * 模拟继承机制， 使得subClass继承自superClass
     * @method inherits
     * @param { Object } subClass 子类对象
     * @param { Object } superClass 超类对象
     * @warning 该方法只能让subClass继承超类的原型， subClass对象自身的属性和方法不会被继承
     * @return { Object } 继承superClass后的子类对象
     * @example
     * ```javascript
     * function SuperClass(){
     *     this.name = "小李";
     * }
     *
     * SuperClass.prototype = {
     *     hello:function(str){
     *         console.log(this.name + str);
     *     }
     * }
     *
     * function SubClass(){
     *     this.name = "小张";
     * }
     *
     * UE.utils.inherits(SubClass,SuperClass);
     *
     * var sub = new SubClass();
     * //output: '小张早上好!
     * sub.hello("早上好!");
     * ```
     */
  inherits: function(subClass, superClass) {
    var oldP = subClass.prototype,
      newP = utils.makeInstance(superClass.prototype);
    utils.extend(newP, oldP, true);
    subClass.prototype = newP;
    return (newP.constructor = subClass);
  },

  /**
     * 用指定的context对象作为函数fn的上下文
     * @method bind
     * @param { Function } fn 需要绑定上下文的函数对象
     * @param { Object } content 函数fn新的上下文对象
     * @return { Function } 一个新的函数， 该函数作为原始函数fn的代理， 将完成fn的上下文调换工作。
     * @example
     * ```javascript
     *
     * var name = 'window',
     *     newTest = null;
     *
     * function test () {
     *     console.log( this.name );
     * }
     *
     * newTest = UE.utils.bind( test, { name: 'object' } );
     *
     * //output: object
     * newTest();
     *
     * //output: window
     * test();
     *
     * ```
     */
  bind: function(fn, context) {
    return function() {
      return fn.apply(context, arguments);
    };
  },

  /**
     * 创建延迟指定时间后执行的函数fn
     * @method defer
     * @param { Function } fn 需要延迟执行的函数对象
     * @param { int } delay 延迟的时间， 单位是毫秒
     * @warning 该方法的时间控制是不精确的，仅仅只能保证函数的执行是在给定的时间之后，
     *           而不能保证刚好到达延迟时间时执行。
     * @return { Function } 目标函数fn的代理函数， 只有执行该函数才能起到延时效果
     * @example
     * ```javascript
     * var start = 0;
     *
     * function test(){
     *     console.log( new Date() - start );
     * }
     *
     * var testDefer = UE.utils.defer( test, 1000 );
     * //
     * start = new Date();
     * //output: (大约在1000毫秒之后输出) 1000
     * testDefer();
     * ```
     */

  /**
     * 创建延迟指定时间后执行的函数fn, 如果在延迟时间内再次执行该方法， 将会根据指定的exclusion的值，
     * 决定是否取消前一次函数的执行， 如果exclusion的值为true， 则取消执行，反之，将继续执行前一个方法。
     * @method defer
     * @param { Function } fn 需要延迟执行的函数对象
     * @param { int } delay 延迟的时间， 单位是毫秒
     * @param { Boolean } exclusion 如果在延迟时间内再次执行该函数，该值将决定是否取消执行前一次函数的执行，
     *                     值为true表示取消执行， 反之则将在执行前一次函数之后才执行本次函数调用。
     * @warning 该方法的时间控制是不精确的，仅仅只能保证函数的执行是在给定的时间之后，
     *           而不能保证刚好到达延迟时间时执行。
     * @return { Function } 目标函数fn的代理函数， 只有执行该函数才能起到延时效果
     * @example
     * ```javascript
     *
     * function test(){
     *     console.log(1);
     * }
     *
     * var testDefer = UE.utils.defer( test, 1000, true );
     *
     * //output: (两次调用仅有一次输出) 1
     * testDefer();
     * testDefer();
     * ```
     */
  defer: function(fn, delay, exclusion) {
    var timerID;
    return function() {
      if (exclusion) {
        clearTimeout(timerID);
      }
      timerID = setTimeout(fn, delay);
    };
  },

  /**
     * 获取元素item在数组array中首次出现的位置, 如果未找到item， 则返回-1
     * @method indexOf
     * @remind 该方法的匹配过程使用的是恒等“===”
     * @param { Array } array 需要查找的数组对象
     * @param { * } item 需要在目标数组中查找的值
     * @return { int } 返回item在目标数组array中首次出现的位置， 如果在数组中未找到item， 则返回-1
     * @example
     * ```javascript
     * var item = 1,
     *     arr = [ 3, 4, 6, 8, 1, 1, 2 ];
     *
     * //output: 4
     * console.log( UE.utils.indexOf( arr, item ) );
     * ```
     */

  /**
     * 获取元素item数组array中首次出现的位置, 如果未找到item， 则返回-1。通过start的值可以指定搜索的起始位置。
     * @method indexOf
     * @remind 该方法的匹配过程使用的是恒等“===”
     * @param { Array } array 需要查找的数组对象
     * @param { * } item 需要在目标数组中查找的值
     * @param { int } start 搜索的起始位置
     * @return { int } 返回item在目标数组array中的start位置之后首次出现的位置， 如果在数组中未找到item， 则返回-1
     * @example
     * ```javascript
     * var item = 1,
     *     arr = [ 3, 4, 6, 8, 1, 2, 8, 3, 2, 1, 1, 4 ];
     *
     * //output: 9
     * console.log( UE.utils.indexOf( arr, item, 5 ) );
     * ```
     */
  indexOf: function(array, item, start) {
    var index = -1;
    start = this.isNumber(start) ? start : 0;
    this.each(array, function(v, i) {
      if (i >= start && v === item) {
        index = i;
        return false;
      }
    });
    return index;
  },

  /**
     * 移除数组array中所有的元素item
     * @method removeItem
     * @param { Array } array 要移除元素的目标数组
     * @param { * } item 将要被移除的元素
     * @remind 该方法的匹配过程使用的是恒等“===”
     * @example
     * ```javascript
     * var arr = [ 4, 5, 7, 1, 3, 4, 6 ];
     *
     * UE.utils.removeItem( arr, 4 );
     * //output: [ 5, 7, 1, 3, 6 ]
     * console.log( arr );
     *
     * ```
     */
  removeItem: function(array, item) {
    for (var i = 0, l = array.length; i < l; i++) {
      if (array[i] === item) {
        array.splice(i, 1);
        i--;
      }
    }
  },

  /**
     * 删除字符串str的首尾空格
     * @method trim
     * @param { String } str 需要删除首尾空格的字符串
     * @return { String } 删除了首尾的空格后的字符串
     * @example
     * ```javascript
     *
     * var str = " UEdtior ";
     *
     * //output: 9
     * console.log( str.length );
     *
     * //output: 7
     * console.log( UE.utils.trim( " UEdtior " ).length );
     *
     * //output: 9
     * console.log( str.length );
     *
     *  ```
     */
  trim: function(str) {
    return str.replace(/(^[ \t\n\r]+)|([ \t\n\r]+$)/g, "");
  },

  /**
     * 将字符串str以','分隔成数组后，将该数组转换成哈希对象， 其生成的hash对象的key为数组中的元素， value为1
     * @method listToMap
     * @warning 该方法在生成的hash对象中，会为每一个key同时生成一个另一个全大写的key。
     * @param { String } str 该字符串将被以','分割为数组， 然后进行转化
     * @return { Object } 转化之后的hash对象
     * @example
     * ```javascript
     *
     * //output: Object {UEdtior: 1, UEDTIOR: 1, Hello: 1, HELLO: 1}
     * console.log( UE.utils.listToMap( 'UEdtior,Hello' ) );
     *
     * ```
     */

  /**
     * 将字符串数组转换成哈希对象， 其生成的hash对象的key为数组中的元素， value为1
     * @method listToMap
     * @warning 该方法在生成的hash对象中，会为每一个key同时生成一个另一个全大写的key。
     * @param { Array } arr 字符串数组
     * @return { Object } 转化之后的hash对象
     * @example
     * ```javascript
     *
     * //output: Object {UEdtior: 1, UEDTIOR: 1, Hello: 1, HELLO: 1}
     * console.log( UE.utils.listToMap( [ 'UEdtior', 'Hello' ] ) );
     *
     * ```
     */
  listToMap: function(list) {
    if (!list) return {};
    list = utils.isArray(list) ? list : list.split(",");
    for (var i = 0, ci, obj = {}; (ci = list[i++]); ) {
      obj[ci.toUpperCase()] = obj[ci] = 1;
    }
    return obj;
  },

  /**
     * 将str中的html符号转义,将转义“'，&，<，"，>，”，“”七个字符
     * @method unhtml
     * @param { String } str 需要转义的字符串
     * @return { String } 转义后的字符串
     * @example
     * ```javascript
     * var html = '<body>&</body>';
     *
     * //output: &lt;body&gt;&amp;&lt;/body&gt;
     * console.log( UE.utils.unhtml( html ) );
     *
     * ```
     */
  unhtml: function(str, reg) {
    return str
      ? str.replace(
          reg || /[&<">'](?:(amp|lt|ldquo|rdquo|quot|gt|#39|nbsp|#\d+);)?/g,
          function(a, b) {
            if (b) {
              return a;
            } else {
              return {
                "<": "&lt;",
                "&": "&amp;",
                '"': "&quot;",
                "“": "&ldquo;",
                "”": "&rdquo;",
                ">": "&gt;",
                "'": "&#39;"
              }[a];
            }
          }
        )
      : "";
  },

  /**
     * 将str中的转义字符还原成html字符
     * @see UE.utils.unhtml(String);
     * @method html
     * @param { String } str 需要逆转义的字符串
     * @return { String } 逆转义后的字符串
     * @example
     * ```javascript
     *
     * var str = '&lt;body&gt;&amp;&lt;/body&gt;';
     *
     * //output: <body>&</body>
     * console.log( UE.utils.html( str ) );
     *
     * ```
     */
  html: function(str) {
    return str
      ? str.replace(/&((g|l|quo|ldquo|rdquo)t|amp|#39|nbsp);/g, function(m) {
          return {
            "&lt;": "<",
            "&amp;": "&",
            "&quot;": '"',
            "&ldquo;": "“",
            "&rdquo;": "”",
            "&gt;": ">",
            "&#39;": "'",
            "&nbsp;": " "
          }[m];
        })
      : "";
  },

  /**
     * 将css样式转换为驼峰的形式
     * @method cssStyleToDomStyle
     * @param { String } cssName 需要转换的css样式名
     * @return { String } 转换成驼峰形式后的css样式名
     * @example
     * ```javascript
     *
     * var str = 'border-top';
     *
     * //output: borderTop
     * console.log( UE.utils.cssStyleToDomStyle( str ) );
     *
     * ```
     */
  cssStyleToDomStyle: (function() {
    var test = document.createElement("div").style,
      cache = {
        float: test.cssFloat !== undefined
          ? "cssFloat"
          : test.styleFloat !== undefined ? "styleFloat" : "float"
      };

    return function(cssName) {
      return (
        cache[cssName] ||
        (cache[cssName] = cssName.toLowerCase().replace(/-./g, function(match) {
          return match.charAt(1).toUpperCase();
        }))
      );
    };
  })(),

  /**
     * 动态加载文件到doc中
     * @method loadFile
     * @param { DomDocument } document 需要加载资源文件的文档对象
     * @param { Object } options 加载资源文件的属性集合， 取值请参考代码示例
     * @example
     * ```javascript
     *
     * UE.utils.loadFile( document, {
     *     src:"test.js",
     *     tag:"script",
     *     type:"text/javascript",
     *     defer:"defer"
     * } );
     *
     * ```
     */

  /**
     * 动态加载文件到doc中，加载成功后执行的回调函数fn
     * @method loadFile
     * @param { DomDocument } document 需要加载资源文件的文档对象
     * @param { Object } options 加载资源文件的属性集合， 该集合支持的值是script标签和style标签支持的所有属性。
     * @param { Function } fn 资源文件加载成功之后执行的回调
     * @warning 对于在同一个文档中多次加载同一URL的文件， 该方法会在第一次加载之后缓存该请求，
     *           在此之后的所有同一URL的请求， 将会直接触发回调。
     * @example
     * ```javascript
     *
     * UE.utils.loadFile( document, {
     *     src:"test.js",
     *     tag:"script",
     *     type:"text/javascript",
     *     defer:"defer"
     * }, function () {
     *     console.log('加载成功');
     * } );
     *
     * ```
     */
  loadFile: (function() {
    var tmpList = [];

    function getItem(doc, obj) {
      try {
        for (var i = 0, ci; (ci = tmpList[i++]); ) {
          if (ci.doc === doc && ci.url == (obj.src || obj.href)) {
            return ci;
          }
        }
      } catch (e) {
        return null;
      }
    }

    return function(doc, obj, fn) {
      var item = getItem(doc, obj);
      if (item) {
        if (item.ready) {
          fn && fn();
        } else {
          item.funs.push(fn);
        }
        return;
      }
      tmpList.push({
        doc: doc,
        url: obj.src || obj.href,
        funs: [fn]
      });
      if (!doc.body) {
        var html = [];
        for (var p in obj) {
          if (p == "tag") continue;
          html.push(p + '="' + obj[p] + '"');
        }
        doc.write(
          "<" + obj.tag + " " + html.join(" ") + " ></" + obj.tag + ">"
        );
        return;
      }
      if (obj.id && doc.getElementById(obj.id)) {
        return;
      }
      var element = doc.createElement(obj.tag);
      delete obj.tag;
      for (var p in obj) {
        element.setAttribute(p, obj[p]);
      }
      element.onload = element.onreadystatechange = function() {
        if (!this.readyState || /loaded|complete/.test(this.readyState)) {
          item = getItem(doc, obj);
          if (item.funs.length > 0) {
            item.ready = 1;
            for (var fi; (fi = item.funs.pop()); ) {
              fi();
            }
          }
          element.onload = element.onreadystatechange = null;
        }
      };
      element.onerror = function() {
        throw Error(
          "The load " +
            (obj.href || obj.src) +
            " fails,check the url settings of file ueditor.config.js "
        );
      };
      doc.getElementsByTagName("head")[0].appendChild(element);
    };
  })(),

  /**
     * 判断obj对象是否为空
     * @method isEmptyObject
     * @param { * } obj 需要判断的对象
     * @remind 如果判断的对象是NULL， 将直接返回true， 如果是数组且为空， 返回true， 如果是字符串， 且字符串为空，
     *          返回true， 如果是普通对象， 且该对象没有任何实例属性， 返回true
     * @return { Boolean } 对象是否为空
     * @example
     * ```javascript
     *
     * //output: true
     * console.log( UE.utils.isEmptyObject( {} ) );
     *
     * //output: true
     * console.log( UE.utils.isEmptyObject( [] ) );
     *
     * //output: true
     * console.log( UE.utils.isEmptyObject( "" ) );
     *
     * //output: false
     * console.log( UE.utils.isEmptyObject( { key: 1 } ) );
     *
     * //output: false
     * console.log( UE.utils.isEmptyObject( [1] ) );
     *
     * //output: false
     * console.log( UE.utils.isEmptyObject( "1" ) );
     *
     * ```
     */
  isEmptyObject: function(obj) {
    if (obj == null) return true;
    if (this.isArray(obj) || this.isString(obj)) return obj.length === 0;
    for (var key in obj) if (obj.hasOwnProperty(key)) return false;
    return true;
  },

  /**
     * 把rgb格式的颜色值转换成16进制格式
     * @method fixColor
     * @param { String } rgb格式的颜色值
     * @param { String }
     * @example
     * rgb(255,255,255)  => "#ffffff"
     */
  fixColor: function(name, value) {
    if (/color/i.test(name) && /rgba?/.test(value)) {
      var array = value.split(",");
      if (array.length > 3) return "";
      value = "#";
      for (var i = 0, color; (color = array[i++]); ) {
        color = parseInt(color.replace(/[^\d]/gi, ""), 10).toString(16);
        value += color.length == 1 ? "0" + color : color;
      }
      value = value.toUpperCase();
    }
    return value;
  },
  /**
     * 只针对border,padding,margin做了处理，因为性能问题
     * @public
     * @function
     * @param {String}    val style字符串
     */
  optCss: function(val) {
    var padding, margin, border;
    val = val.replace(/(padding|margin|border)\-([^:]+):([^;]+);?/gi, function(
      str,
      key,
      name,
      val
    ) {
      if (val.split(" ").length == 1) {
        switch (key) {
          case "padding":
            !padding && (padding = {});
            padding[name] = val;
            return "";
          case "margin":
            !margin && (margin = {});
            margin[name] = val;
            return "";
          case "border":
            return val == "initial" ? "" : str;
        }
      }
      return str;
    });

    function opt(obj, name) {
      if (!obj) {
        return "";
      }
      var t = obj.top,
        b = obj.bottom,
        l = obj.left,
        r = obj.right,
        val = "";
      if (!t || !l || !b || !r) {
        for (var p in obj) {
          val += ";" + name + "-" + p + ":" + obj[p] + ";";
        }
      } else {
        val +=
          ";" +
          name +
          ":" +
          (t == b && b == l && l == r
            ? t
            : t == b && l == r
              ? t + " " + l
              : l == r
                ? t + " " + l + " " + b
                : t + " " + r + " " + b + " " + l) +
          ";";
      }
      return val;
    }

    val += opt(padding, "padding") + opt(margin, "margin");
    return val
      .replace(/^[ \n\r\t;]*|[ \n\r\t]*$/, "")
      .replace(/;([ \n\r\t]+)|\1;/g, ";")
      .replace(/(&((l|g)t|quot|#39))?;{2,}/g, function(a, b) {
        return b ? b + ";;" : ";";
      });
  },

  /**
     * 克隆对象
     * @method clone
     * @param { Object } source 源对象
     * @return { Object } source的一个副本
     */

  /**
     * 深度克隆对象，将source的属性克隆到target对象， 会覆盖target重名的属性。
     * @method clone
     * @param { Object } source 源对象
     * @param { Object } target 目标对象
     * @return { Object } 附加了source对象所有属性的target对象
     */
  clone: function(source, target) {
    var tmp;
    target = target || {};
    for (var i in source) {
      if (source.hasOwnProperty(i)) {
        tmp = source[i];
        if (typeof tmp == "object") {
          target[i] = utils.isArray(tmp) ? [] : {};
          utils.clone(source[i], target[i]);
        } else {
          target[i] = tmp;
        }
      }
    }
    return target;
  },

  /**
     * 把cm／pt为单位的值转换为px为单位的值
     * @method transUnitToPx
     * @param { String } 待转换的带单位的字符串
     * @return { String } 转换为px为计量单位的值的字符串
     * @example
     * ```javascript
     *
     * //output: 500px
     * console.log( UE.utils.transUnitToPx( '20cm' ) );
     *
     * //output: 27px
     * console.log( UE.utils.transUnitToPx( '20pt' ) );
     *
     * ```
     */
  transUnitToPx: function(val) {
    if (!/(pt|cm)/.test(val)) {
      return val;
    }
    var unit;
    val.replace(/([\d.]+)(\w+)/, function(str, v, u) {
      val = v;
      unit = u;
    });
    switch (unit) {
      case "cm":
        val = parseFloat(val) * 25;
        break;
      case "pt":
        val = Math.round(parseFloat(val) * 96 / 72);
    }
    return val + (val ? "px" : "");
  },

  /**
     * 在dom树ready之后执行给定的回调函数
     * @method domReady
     * @remind 如果在执行该方法的时候， dom树已经ready， 那么回调函数将立刻执行
     * @param { Function } fn dom树ready之后的回调函数
     * @example
     * ```javascript
     *
     * UE.utils.domReady( function () {
     *
     *     console.log('123');
     *
     * } );
     *
     * ```
     */
  domReady: (function() {
    var fnArr = [];

    function doReady(doc) {
      //确保onready只执行一次
      doc.isReady = true;
      for (var ci; (ci = fnArr.pop()); ci()) {}
    }

    return function(onready, win) {
      win = win || window;
      var doc = win.document;
      onready && fnArr.push(onready);
      if (doc.readyState === "complete") {
        doReady(doc);
      } else {
        doc.isReady && doReady(doc);
        if (browser.ie && browser.version != 11) {
          (function() {
            if (doc.isReady) return;
            try {
              doc.documentElement.doScroll("left");
            } catch (error) {
              setTimeout(arguments.callee, 0);
              return;
            }
            doReady(doc);
          })();
          win.attachEvent("onload", function() {
            doReady(doc);
          });
        } else {
          doc.addEventListener(
            "DOMContentLoaded",
            function() {
              doc.removeEventListener(
                "DOMContentLoaded",
                arguments.callee,
                false
              );
              doReady(doc);
            },
            false
          );
          win.addEventListener(
            "load",
            function() {
              doReady(doc);
            },
            false
          );
        }
      }
    };
  })(),

  /**
     * 动态添加css样式
     * @method cssRule
     * @param { String } 节点名称
     * @grammar UE.utils.cssRule('添加的样式的节点名称',['样式'，'放到哪个document上'])
     * @grammar UE.utils.cssRule('body','body{background:#ccc}') => null  //给body添加背景颜色
     * @grammar UE.utils.cssRule('body') =>样式的字符串  //取得key值为body的样式的内容,如果没有找到key值先关的样式将返回空，例如刚才那个背景颜色，将返回 body{background:#ccc}
     * @grammar UE.utils.cssRule('body',document) => 返回指定key的样式，并且指定是哪个document
     * @grammar UE.utils.cssRule('body','') =>null //清空给定的key值的背景颜色
     */
  cssRule: browser.ie && browser.version != 11
    ? function(key, style, doc) {
        var indexList, index;
        if (
          style === undefined ||
          (style && style.nodeType && style.nodeType == 9)
        ) {
          //获取样式
          doc = style && style.nodeType && style.nodeType == 9
            ? style
            : doc || document;
          indexList = doc.indexList || (doc.indexList = {});
          index = indexList[key];
          if (index !== undefined) {
            return doc.styleSheets[index].cssText;
          }
          return undefined;
        }
        doc = doc || document;
        indexList = doc.indexList || (doc.indexList = {});
        index = indexList[key];
        //清除样式
        if (style === "") {
          if (index !== undefined) {
            doc.styleSheets[index].cssText = "";
            delete indexList[key];
            return true;
          }
          return false;
        }

        //添加样式
        if (index !== undefined) {
          sheetStyle = doc.styleSheets[index];
        } else {
          sheetStyle = doc.createStyleSheet(
            "",
            (index = doc.styleSheets.length)
          );
          indexList[key] = index;
        }
        sheetStyle.cssText = style;
      }
    : function(key, style, doc) {
        var head, node;
        if (
          style === undefined ||
          (style && style.nodeType && style.nodeType == 9)
        ) {
          //获取样式
          doc = style && style.nodeType && style.nodeType == 9
            ? style
            : doc || document;
          node = doc.getElementById(key);
          return node ? node.innerHTML : undefined;
        }
        doc = doc || document;
        node = doc.getElementById(key);

        //清除样式
        if (style === "") {
          if (node) {
            node.parentNode.removeChild(node);
            return true;
          }
          return false;
        }

        //添加样式
        if (node) {
          node.innerHTML = style;
        } else {
          node = doc.createElement("style");
          node.id = key;
          node.innerHTML = style;
          doc.getElementsByTagName("head")[0].appendChild(node);
        }
      },
  sort: function(array, compareFn) {
    compareFn =
      compareFn ||
      function(item1, item2) {
        return item1.localeCompare(item2);
      };
    for (var i = 0, len = array.length; i < len; i++) {
      for (var j = i, length = array.length; j < length; j++) {
        if (compareFn(array[i], array[j]) > 0) {
          var t = array[i];
          array[i] = array[j];
          array[j] = t;
        }
      }
    }
    return array;
  },
  serializeParam: function(json) {
    var strArr = [];
    for (var i in json) {
      //忽略默认的几个参数
      if (i == "method" || i == "timeout" || i == "async") continue;
      //传递过来的对象和函数不在提交之列
      if (
        !(
          (typeof json[i]).toLowerCase() == "function" ||
          (typeof json[i]).toLowerCase() == "object"
        )
      ) {
        strArr.push(encodeURIComponent(i) + "=" + encodeURIComponent(json[i]));
      } else if (utils.isArray(json[i])) {
        //支持传数组内容
        for (var j = 0; j < json[i].length; j++) {
          strArr.push(
            encodeURIComponent(i) + "[]=" + encodeURIComponent(json[i][j])
          );
        }
      }
    }
    return strArr.join("&");
  },
  formatUrl: function(url) {
    var u = url.replace(/&&/g, "&");
    u = u.replace(/\?&/g, "?");
    u = u.replace(/&$/g, "");
    u = u.replace(/&#/g, "#");
    u = u.replace(/&+/g, "&");
    return u;
  },
  addStyleContent: function(cssContent){
    var style = document.createElement("style");
    style.innerHTML = cssContent;
    document.head.appendChild(style);
  },
  isCrossDomainUrl: function(url) {
    var a = document.createElement("a");
    a.href = url;
    if (browser.ie) {
      a.href = a.href;
    }
    return !(
      a.protocol == location.protocol &&
      a.hostname == location.hostname &&
      (a.port == location.port ||
        (a.port == "80" && location.port == "") ||
        (a.port == "" && location.port == "80"))
    );
  },
  clearEmptyAttrs: function(obj) {
    for (var p in obj) {
      if (obj[p] === "") {
        delete obj[p];
      }
    }
    return obj;
  },
  str2json: function(s) {
    if (!utils.isString(s)) return null;
    if (window.JSON) {
      return JSON.parse(s);
    } else {
      return new Function("return " + utils.trim(s || ""))();
    }
  },
  json2str: (function() {
    if (window.JSON) {
      return JSON.stringify;
    } else {
      var escapeMap = {
        "\b": "\\b",
        "\t": "\\t",
        "\n": "\\n",
        "\f": "\\f",
        "\r": "\\r",
        '"': '\\"',
        "\\": "\\\\"
      };

      function encodeString(source) {
        if (/["\\\x00-\x1f]/.test(source)) {
          source = source.replace(/["\\\x00-\x1f]/g, function(match) {
            var c = escapeMap[match];
            if (c) {
              return c;
            }
            c = match.charCodeAt();
            return (
              "\\u00" + Math.floor(c / 16).toString(16) + (c % 16).toString(16)
            );
          });
        }
        return '"' + source + '"';
      }

      function encodeArray(source) {
        var result = ["["],
          l = source.length,
          preComma,
          i,
          item;

        for (i = 0; i < l; i++) {
          item = source[i];

          switch (typeof item) {
            case "undefined":
            case "function":
            case "unknown":
              break;
            default:
              if (preComma) {
                result.push(",");
              }
              result.push(utils.json2str(item));
              preComma = 1;
          }
        }
        result.push("]");
        return result.join("");
      }

      function pad(source) {
        return source < 10 ? "0" + source : source;
      }

      function encodeDate(source) {
        return (
          '"' +
          source.getFullYear() +
          "-" +
          pad(source.getMonth() + 1) +
          "-" +
          pad(source.getDate()) +
          "T" +
          pad(source.getHours()) +
          ":" +
          pad(source.getMinutes()) +
          ":" +
          pad(source.getSeconds()) +
          '"'
        );
      }

      return function(value) {
        switch (typeof value) {
          case "undefined":
            return "undefined";

          case "number":
            return isFinite(value) ? String(value) : "null";

          case "string":
            return encodeString(value);

          case "boolean":
            return String(value);

          default:
            if (value === null) {
              return "null";
            } else if (utils.isArray(value)) {
              return encodeArray(value);
            } else if (utils.isDate(value)) {
              return encodeDate(value);
            } else {
              var result = ["{"],
                encode = utils.json2str,
                preComma,
                item;

              for (var key in value) {
                if (Object.prototype.hasOwnProperty.call(value, key)) {
                  item = value[key];
                  switch (typeof item) {
                    case "undefined":
                    case "unknown":
                    case "function":
                      break;
                    default:
                      if (preComma) {
                        result.push(",");
                      }
                      preComma = 1;
                      result.push(encode(key) + ":" + encode(item));
                  }
                }
              }
              result.push("}");
              return result.join("");
            }
        }
      };
    }
  })()
});
/**
 * 判断给定的对象是否是字符串
 * @method isString
 * @param { * } object 需要判断的对象
 * @return { Boolean } 给定的对象是否是字符串
 */

/**
 * 判断给定的对象是否是数组
 * @method isArray
 * @param { * } object 需要判断的对象
 * @return { Boolean } 给定的对象是否是数组
 */

/**
 * 判断给定的对象是否是一个Function
 * @method isFunction
 * @param { * } object 需要判断的对象
 * @return { Boolean } 给定的对象是否是Function
 */

/**
 * 判断给定的对象是否是Number
 * @method isNumber
 * @param { * } object 需要判断的对象
 * @return { Boolean } 给定的对象是否是Number
 */

/**
 * 判断给定的对象是否是一个正则表达式
 * @method isRegExp
 * @param { * } object 需要判断的对象
 * @return { Boolean } 给定的对象是否是正则表达式
 */

/**
 * 判断给定的对象是否是一个普通对象
 * @method isObject
 * @param { * } object 需要判断的对象
 * @return { Boolean } 给定的对象是否是普通对象
 */
utils.each(
  ["String", "Function", "Array", "Number", "RegExp", "Object", "Date"],
  function(v) {
    UE.utils["is" + v] = function(obj) {
      return Object.prototype.toString.apply(obj) == "[object " + v + "]";
    };
  }
);


// core/EventBase.js
/**
 * UE采用的事件基类
 * @file
 * @module UE
 * @class EventBase
 * @since 1.2.6.1
 */

/**
 * UEditor公用空间，UEditor所有的功能都挂载在该空间下
 * @unfile
 * @module UE
 */

/**
 * UE采用的事件基类，继承此类的对应类将获取addListener,removeListener,fireEvent方法。
 * 在UE中，Editor以及所有ui实例都继承了该类，故可以在对应的ui对象以及editor对象上使用上述方法。
 * @unfile
 * @module UE
 * @class EventBase
 */

/**
 * 通过此构造器，子类可以继承EventBase获取事件监听的方法
 * @constructor
 * @example
 * ```javascript
 * UE.EventBase.call(editor);
 * ```
 */
var EventBase = (UE.EventBase = function() {});

EventBase.prototype = {
  /**
     * 注册事件监听器
     * @method addListener
     * @param { String } types 监听的事件名称，同时监听多个事件使用空格分隔
     * @param { Function } fn 监听的事件被触发时，会执行该回调函数
     * @waining 事件被触发时，监听的函数假如返回的值恒等于true，回调函数的队列中后面的函数将不执行
     * @example
     * ```javascript
     * editor.addListener('selectionchange',function(){
     *      console.log("选区已经变化！");
     * })
     * editor.addListener('beforegetcontent aftergetcontent',function(type){
     *         if(type == 'beforegetcontent'){
     *             //do something
     *         }else{
     *             //do something
     *         }
     *         console.log(this.getContent) // this是注册的事件的编辑器实例
     * })
     * ```
     * @see UE.EventBase:fireEvent(String)
     */
  addListener: function(types, listener) {
    types = utils.trim(types).split(/\s+/);
    for (var i = 0, ti; (ti = types[i++]); ) {
      getListener(this, ti, true).push(listener);
    }
  },

  on: function(types, listener) {
    return this.addListener(types, listener);
  },
  off: function(types, listener) {
    return this.removeListener(types, listener);
  },
  trigger: function() {
    return this.fireEvent.apply(this, arguments);
  },
  /**
     * 移除事件监听器
     * @method removeListener
     * @param { String } types 移除的事件名称，同时移除多个事件使用空格分隔
     * @param { Function } fn 移除监听事件的函数引用
     * @example
     * ```javascript
     * //changeCallback为方法体
     * editor.removeListener("selectionchange",changeCallback);
     * ```
     */
  removeListener: function(types, listener) {
    types = utils.trim(types).split(/\s+/);
    for (var i = 0, ti; (ti = types[i++]); ) {
      utils.removeItem(getListener(this, ti) || [], listener);
    }
  },

  /**
     * 触发事件
     * @method fireEvent
     * @param { String } types 触发的事件名称，同时触发多个事件使用空格分隔
     * @remind 该方法会触发addListener
     * @return { * } 返回触发事件的队列中，最后执行的回调函数的返回值
     * @example
     * ```javascript
     * editor.fireEvent("selectionchange");
     * ```
     */

  /**
     * 触发事件
     * @method fireEvent
     * @param { String } types 触发的事件名称，同时触发多个事件使用空格分隔
     * @param { *... } options 可选参数，可以传入一个或多个参数，会传给事件触发的回调函数
     * @return { * } 返回触发事件的队列中，最后执行的回调函数的返回值
     * @example
     * ```javascript
     *
     * editor.addListener( "selectionchange", function ( type, arg1, arg2 ) {
     *
     *     console.log( arg1 + " " + arg2 );
     *
     * } );
     *
     * //触发selectionchange事件， 会执行上面的事件监听器
     * //output: Hello World
     * editor.fireEvent("selectionchange", "Hello", "World");
     * ```
     */
  fireEvent: function() {
    var types = arguments[0];
    types = utils.trim(types).split(" ");
    for (var i = 0, ti; (ti = types[i++]); ) {
      var listeners = getListener(this, ti),
        r,
        t,
        k;
      if (listeners) {
        k = listeners.length;
        while (k--) {
          if (!listeners[k]) continue;
          t = listeners[k].apply(this, arguments);
          if (t === true) {
            return t;
          }
          if (t !== undefined) {
            r = t;
          }
        }
      }
      if ((t = this["on" + ti.toLowerCase()])) {
        r = t.apply(this, arguments);
      }
    }
    return r;
  }
};
/**
 * 获得对象所拥有监听类型的所有监听器
 * @unfile
 * @module UE
 * @since 1.2.6.1
 * @method getListener
 * @public
 * @param { Object } obj  查询监听器的对象
 * @param { String } type 事件类型
 * @param { Boolean } force  为true且当前所有type类型的侦听器不存在时，创建一个空监听器数组
 * @return { Array } 监听器数组
 */
function getListener(obj, type, force) {
  var allListeners;
  type = type.toLowerCase();
  return (
    (allListeners =
      obj.__allListeners || (force && (obj.__allListeners = {}))) &&
    (allListeners[type] || (force && (allListeners[type] = [])))
  );
}


// core/dtd.js
///import editor.js
///import core/dom/dom.js
///import core/utils.js
/**
 * dtd html语义化的体现类
 * @constructor
 * @namespace dtd
 */
var dtd = (dom.dtd = (function() {
  function _(s) {
    for (var k in s) {
      s[k.toUpperCase()] = s[k];
    }
    return s;
  }
  var X = utils.extend2;
  var A = _({ isindex: 1, fieldset: 1 }),
    B = _({ input: 1, button: 1, select: 1, textarea: 1, label: 1 }),
    C = X(_({ a: 1 }), B),
    D = X({ iframe: 1 }, C),
    E = _({
      hr: 1,
      ul: 1,
      menu: 1,
      div: 1,
      blockquote: 1,
      noscript: 1,
      table: 1,
      center: 1,
      address: 1,
      dir: 1,
      pre: 1,
      h5: 1,
      dl: 1,
      h4: 1,
      noframes: 1,
      h6: 1,
      ol: 1,
      h1: 1,
      h3: 1,
      h2: 1
    }),
    F = _({ ins: 1, del: 1, script: 1, style: 1 }),
    G = X(
      _({
        mark: 1,
        b: 1,
        acronym: 1,
        bdo: 1,
        var: 1,
        "#": 1,
        abbr: 1,
        code: 1,
        br: 1,
        i: 1,
        cite: 1,
        kbd: 1,
        u: 1,
        strike: 1,
        s: 1,
        tt: 1,
        strong: 1,
        q: 1,
        samp: 1,
        em: 1,
        dfn: 1,
        span: 1
      }),
      F
    ),
    H = X(
      _({
        sub: 1,
        img: 1,
        embed: 1,
        object: 1,
        sup: 1,
        basefont: 1,
        map: 1,
        applet: 1,
        font: 1,
        big: 1,
        small: 1
      }),
      G
    ),
    I = X(_({ p: 1 }), H),
    J = X(_({ iframe: 1 }), H, B),
    K = _({
      img: 1,
      embed: 1,
      noscript: 1,
      br: 1,
      kbd: 1,
      center: 1,
      button: 1,
      basefont: 1,
      h5: 1,
      h4: 1,
      samp: 1,
      h6: 1,
      ol: 1,
      h1: 1,
      h3: 1,
      h2: 1,
      form: 1,
      font: 1,
      "#": 1,
      select: 1,
      menu: 1,
      ins: 1,
      abbr: 1,
      label: 1,
      code: 1,
      table: 1,
      script: 1,
      cite: 1,
      input: 1,
      iframe: 1,
      strong: 1,
      textarea: 1,
      noframes: 1,
      big: 1,
      small: 1,
      span: 1,
      hr: 1,
      sub: 1,
      bdo: 1,
      var: 1,
      div: 1,
      object: 1,
      sup: 1,
      strike: 1,
      dir: 1,
      map: 1,
      dl: 1,
      applet: 1,
      del: 1,
      isindex: 1,
      fieldset: 1,
      ul: 1,
      b: 1,
      acronym: 1,
      a: 1,
      blockquote: 1,
      i: 1,
      u: 1,
      s: 1,
      tt: 1,
      address: 1,
      q: 1,
      pre: 1,
      p: 1,
      em: 1,
      dfn: 1
    }),
    L = X(_({ a: 0 }), J), //a不能被切开，所以把他
    M = _({ tr: 1 }),
    N = _({ "#": 1 }),
    O = X(_({ param: 1 }), K),
    P = X(_({ form: 1 }), A, D, E, I),
    Q = _({ li: 1, ol: 1, ul: 1 }),
    R = _({ style: 1, script: 1 }),
    S = _({ base: 1, link: 1, meta: 1, title: 1 }),
    T = X(S, R),
    U = _({ head: 1, body: 1 }),
    V = _({ html: 1 });

  var block = _({
    address: 1,
    blockquote: 1,
    center: 1,
    dir: 1,
    div: 1,
    dl: 1,
    fieldset: 1,
    form: 1,
    h1: 1,
    h2: 1,
    h3: 1,
    h4: 1,
    h5: 1,
    h6: 1,
    hr: 1,
    isindex: 1,
    menu: 1,
    noframes: 1,
    ol: 1,
    p: 1,
    pre: 1,
    table: 1,
    ul: 1
  }),
    empty = _({
      area: 1,
      base: 1,
      basefont: 1,
      br: 1,
      col: 1,
      command: 1,
      dialog: 1,
      embed: 1,
      hr: 1,
      img: 1,
      input: 1,
      isindex: 1,
      keygen: 1,
      link: 1,
      meta: 1,
      param: 1,
      source: 1,
      track: 1,
      wbr: 1
    });

  return _({
    // $ 表示自定的属性

    // body外的元素列表.
    $nonBodyContent: X(V, U, S),

    //块结构元素列表
    $block: block,

    //内联元素列表
    $inline: L,

    $inlineWithA: X(_({ a: 1 }), L),

    $body: X(_({ script: 1, style: 1 }), block),

    $cdata: _({ script: 1, style: 1 }),

    //自闭和元素
    $empty: empty,

    //不是自闭合，但不能让range选中里边
    $nonChild: _({ iframe: 1, textarea: 1 }),
    //列表元素列表
    $listItem: _({ dd: 1, dt: 1, li: 1 }),

    //列表根元素列表
    $list: _({ ul: 1, ol: 1, dl: 1 }),

    //不能认为是空的元素
    $isNotEmpty: _({
      table: 1,
      ul: 1,
      ol: 1,
      dl: 1,
      iframe: 1,
      area: 1,
      base: 1,
      col: 1,
      hr: 1,
      img: 1,
      embed: 1,
      input: 1,
      textarea: 1,
      link: 1,
      meta: 1,
      param: 1,
      h1: 1,
      h2: 1,
      h3: 1,
      h4: 1,
      h5: 1,
      h6: 1
    }),

    //如果没有子节点就可以删除的元素列表，像span,a
    $removeEmpty: _({
      a: 1,
      abbr: 1,
      acronym: 1,
      address: 1,
      b: 1,
      bdo: 1,
      big: 1,
      cite: 1,
      code: 1,
      del: 1,
      dfn: 1,
      em: 1,
      font: 1,
      i: 1,
      ins: 1,
      label: 1,
      kbd: 1,
      q: 1,
      s: 1,
      samp: 1,
      small: 1,
      span: 1,
      strike: 1,
      strong: 1,
      sub: 1,
      sup: 1,
      tt: 1,
      u: 1,
      var: 1
    }),

    $removeEmptyBlock: _({ p: 1, div: 1 }),

    //在table元素里的元素列表
    $tableContent: _({
      caption: 1,
      col: 1,
      colgroup: 1,
      tbody: 1,
      td: 1,
      tfoot: 1,
      th: 1,
      thead: 1,
      tr: 1,
      table: 1
    }),
    //不转换的标签
    $notTransContent: _({ pre: 1, script: 1, style: 1, textarea: 1 }),
    html: U,
    head: T,
    style: N,
    script: N,
    body: P,
    base: {},
    link: {},
    meta: {},
    title: N,
    col: {},
    tr: _({ td: 1, th: 1 }),
    img: {},
    embed: {},
    colgroup: _({ thead: 1, col: 1, tbody: 1, tr: 1, tfoot: 1 }),
    noscript: P,
    td: P,
    br: {},
    th: P,
    center: P,
    kbd: L,
    button: X(I, E),
    basefont: {},
    h5: L,
    h4: L,
    samp: L,
    h6: L,
    ol: Q,
    h1: L,
    h3: L,
    option: N,
    h2: L,
    form: X(A, D, E, I),
    select: _({ optgroup: 1, option: 1 }),
    font: L,
    ins: L,
    menu: Q,
    abbr: L,
    label: L,
    table: _({
      thead: 1,
      col: 1,
      tbody: 1,
      tr: 1,
      colgroup: 1,
      caption: 1,
      tfoot: 1
    }),
    code: L,
    tfoot: M,
    cite: L,
    li: P,
    input: {},
    iframe: P,
    strong: L,
    textarea: N,
    noframes: P,
    big: L,
    small: L,
    //trace:
    span: _({
      "#": 1,
      br: 1,
      b: 1,
      strong: 1,
      u: 1,
      i: 1,
      em: 1,
      sub: 1,
      sup: 1,
      strike: 1,
      span: 1
    }),
    hr: L,
    dt: L,
    sub: L,
    optgroup: _({ option: 1 }),
    param: {},
    bdo: L,
    var: L,
    div: P,
    object: O,
    sup: L,
    dd: P,
    strike: L,
    area: {},
    dir: Q,
    map: X(_({ area: 1, form: 1, p: 1 }), A, F, E),
    applet: O,
    dl: _({ dt: 1, dd: 1 }),
    del: L,
    isindex: {},
    fieldset: X(_({ legend: 1 }), K),
    thead: M,
    ul: Q,
    acronym: L,
    b: L,
    a: X(_({ a: 1 }), J),
    blockquote: X(_({ td: 1, tr: 1, tbody: 1, li: 1 }), P),
    caption: L,
    i: L,
    u: L,
    tbody: M,
    s: L,
    address: X(D, I),
    tt: L,
    legend: L,
    q: L,
    pre: X(G, C),
    p: X(_({ a: 1 }), L),
    em: L,
    dfn: L,
    mark: L
  });
})());


// core/domUtils.js
/**
 * Dom操作工具包
 * @file
 * @module UE.dom.domUtils
 * @since 1.2.6.1
 */

/**
 * Dom操作工具包
 * @unfile
 * @module UE.dom.domUtils
 */
function getDomNode(node, start, ltr, startFromChild, fn, guard) {
  var tmpNode = startFromChild && node[start],
    parent;
  !tmpNode && (tmpNode = node[ltr]);
  while (!tmpNode && (parent = (parent || node).parentNode)) {
    if (parent.tagName == "BODY" || (guard && !guard(parent))) {
      return null;
    }
    tmpNode = parent[ltr];
  }
  if (tmpNode && fn && !fn(tmpNode)) {
    return getDomNode(tmpNode, start, ltr, false, fn);
  }
  return tmpNode;
}
var attrFix = ie && browser.version < 9
  ? {
      tabindex: "tabIndex",
      readonly: "readOnly",
      for: "htmlFor",
      class: "className",
      maxlength: "maxLength",
      cellspacing: "cellSpacing",
      cellpadding: "cellPadding",
      rowspan: "rowSpan",
      colspan: "colSpan",
      usemap: "useMap",
      frameborder: "frameBorder"
    }
  : {
      tabindex: "tabIndex",
      readonly: "readOnly"
    },
  styleBlock = utils.listToMap([
    "-webkit-box",
    "-moz-box",
    "block",
    "list-item",
    "table",
    "table-row-group",
    "table-header-group",
    "table-footer-group",
    "table-row",
    "table-column-group",
    "table-column",
    "table-cell",
    "table-caption"
  ]);
var domUtils = (dom.domUtils = {
  //节点常量
  NODE_ELEMENT: 1,
  NODE_DOCUMENT: 9,
  NODE_TEXT: 3,
  NODE_COMMENT: 8,
  NODE_DOCUMENT_FRAGMENT: 11,

  //位置关系
  POSITION_IDENTICAL: 0,
  POSITION_DISCONNECTED: 1,
  POSITION_FOLLOWING: 2,
  POSITION_PRECEDING: 4,
  POSITION_IS_CONTAINED: 8,
  POSITION_CONTAINS: 16,
  //ie6使用其他的会有一段空白出现
  fillChar: ie && browser.version === "6" ? "\ufeff" : "\u200B",
  //-------------------------Node部分--------------------------------
  keys: {
    /*Backspace*/ 8: 1,
    /*Delete*/ 46: 1,
    /*Shift*/ 16: 1,
    /*Ctrl*/ 17: 1,
    /*Alt*/ 18: 1,
    37: 1,
    38: 1,
    39: 1,
    40: 1,
    13: 1 /*enter*/
  },
  /**
     * 获取节点A相对于节点B的位置关系
     * @method getPosition
     * @param { Node } nodeA 需要查询位置关系的节点A
     * @param { Node } nodeB 需要查询位置关系的节点B
     * @return { Number } 节点A与节点B的关系
     * @example
     * ```javascript
     * //output: 20
     * var position = UE.dom.domUtils.getPosition( document.documentElement, document.body );
     *
     * switch ( position ) {
     *
     *      //0
     *      case UE.dom.domUtils.POSITION_IDENTICAL:
     *          console.log('元素相同');
     *          break;
     *      //1
     *      case UE.dom.domUtils.POSITION_DISCONNECTED:
     *          console.log('两个节点在不同的文档中');
     *          break;
     *      //2
     *      case UE.dom.domUtils.POSITION_FOLLOWING:
     *          console.log('节点A在节点B之后');
     *          break;
     *      //4
     *      case UE.dom.domUtils.POSITION_PRECEDING;
     *          console.log('节点A在节点B之前');
     *          break;
     *      //8
     *      case UE.dom.domUtils.POSITION_IS_CONTAINED:
     *          console.log('节点A被节点B包含');
     *          break;
     *      case 10:
     *          console.log('节点A被节点B包含且节点A在节点B之后');
     *          break;
     *      //16
     *      case UE.dom.domUtils.POSITION_CONTAINS:
     *          console.log('节点A包含节点B');
     *          break;
     *      case 20:
     *          console.log('节点A包含节点B且节点A在节点B之前');
     *          break;
     *
     * }
     * ```
     */
  getPosition: function(nodeA, nodeB) {
    // 如果两个节点是同一个节点
    if (nodeA === nodeB) {
      // domUtils.POSITION_IDENTICAL
      return 0;
    }
    var node,
      parentsA = [nodeA],
      parentsB = [nodeB];
    node = nodeA;
    while ((node = node.parentNode)) {
      // 如果nodeB是nodeA的祖先节点
      if (node === nodeB) {
        // domUtils.POSITION_IS_CONTAINED + domUtils.POSITION_FOLLOWING
        return 10;
      }
      parentsA.push(node);
    }
    node = nodeB;
    while ((node = node.parentNode)) {
      // 如果nodeA是nodeB的祖先节点
      if (node === nodeA) {
        // domUtils.POSITION_CONTAINS + domUtils.POSITION_PRECEDING
        return 20;
      }
      parentsB.push(node);
    }
    parentsA.reverse();
    parentsB.reverse();
    if (parentsA[0] !== parentsB[0]) {
      // domUtils.POSITION_DISCONNECTED
      return 1;
    }
    var i = -1;
    while ((i++, parentsA[i] === parentsB[i])) {}
    nodeA = parentsA[i];
    nodeB = parentsB[i];
    while ((nodeA = nodeA.nextSibling)) {
      if (nodeA === nodeB) {
        // domUtils.POSITION_PRECEDING
        return 4;
      }
    }
    // domUtils.POSITION_FOLLOWING
    return 2;
  },

  /**
     * 检测节点node在父节点中的索引位置
     * @method getNodeIndex
     * @param { Node } node 需要检测的节点对象
     * @return { Number } 该节点在父节点中的位置
     * @see UE.dom.domUtils.getNodeIndex(Node,Boolean)
     */

  /**
     * 检测节点node在父节点中的索引位置， 根据给定的mergeTextNode参数决定是否要合并多个连续的文本节点为一个节点
     * @method getNodeIndex
     * @param { Node } node 需要检测的节点对象
     * @param { Boolean } mergeTextNode 是否合并多个连续的文本节点为一个节点
     * @return { Number } 该节点在父节点中的位置
     * @example
     * ```javascript
     *
     *      var node = document.createElement("div");
     *
     *      node.appendChild( document.createTextNode( "hello" ) );
     *      node.appendChild( document.createTextNode( "world" ) );
     *      node.appendChild( node = document.createElement( "div" ) );
     *
     *      //output: 2
     *      console.log( UE.dom.domUtils.getNodeIndex( node ) );
     *
     *      //output: 1
     *      console.log( UE.dom.domUtils.getNodeIndex( node, true ) );
     *
     * ```
     */
  getNodeIndex: function(node, ignoreTextNode) {
    var preNode = node,
      i = 0;
    while ((preNode = preNode.previousSibling)) {
      if (ignoreTextNode && preNode.nodeType == 3) {
        if (preNode.nodeType != preNode.nextSibling.nodeType) {
          i++;
        }
        continue;
      }
      i++;
    }
    return i;
  },

  /**
     * 检测节点node是否在给定的document对象上
     * @method inDoc
     * @param { Node } node 需要检测的节点对象
     * @param { DomDocument } doc 需要检测的document对象
     * @return { Boolean } 该节点node是否在给定的document的dom树上
     * @example
     * ```javascript
     *
     * var node = document.createElement("div");
     *
     * //output: false
     * console.log( UE.do.domUtils.inDoc( node, document ) );
     *
     * document.body.appendChild( node );
     *
     * //output: true
     * console.log( UE.do.domUtils.inDoc( node, document ) );
     *
     * ```
     */
  inDoc: function(node, doc) {
    return domUtils.getPosition(node, doc) === 10;
  },
  /**
     * 根据给定的过滤规则filterFn， 查找符合该过滤规则的node节点的第一个祖先节点，
     * 查找的起点是给定node节点的父节点。
     * @method findParent
     * @param { Node } node 需要查找的节点
     * @param { Function } filterFn 自定义的过滤方法。
     * @warning 查找的终点是到body节点为止
     * @remind 自定义的过滤方法filterFn接受一个Node对象作为参数， 该对象代表当前执行检测的祖先节点。 如果该
     *          节点满足过滤条件， 则要求返回true， 这时将直接返回该节点作为findParent()的结果， 否则， 请返回false。
     * @return { Node | Null } 如果找到符合过滤条件的节点， 就返回该节点， 否则返回NULL
     * @example
     * ```javascript
     * var filterNode = UE.dom.domUtils.findParent( document.body.firstChild, function ( node ) {
     *
     *     //由于查找的终点是body节点， 所以永远也不会匹配当前过滤器的条件， 即这里永远会返回false
     *     return node.tagName === "HTML";
     *
     * } );
     *
     * //output: true
     * console.log( filterNode === null );
     * ```
     */

  /**
     * 根据给定的过滤规则filterFn， 查找符合该过滤规则的node节点的第一个祖先节点，
     * 如果includeSelf的值为true，则查找的起点是给定的节点node， 否则， 起点是node的父节点
     * @method findParent
     * @param { Node } node 需要查找的节点
     * @param { Function } filterFn 自定义的过滤方法。
     * @param { Boolean } includeSelf 查找过程是否包含自身
     * @warning 查找的终点是到body节点为止
     * @remind 自定义的过滤方法filterFn接受一个Node对象作为参数， 该对象代表当前执行检测的祖先节点。 如果该
     *          节点满足过滤条件， 则要求返回true， 这时将直接返回该节点作为findParent()的结果， 否则， 请返回false。
     * @remind 如果includeSelf为true， 则过滤器第一次执行时的参数会是节点本身。
     *          反之， 过滤器第一次执行时的参数将是该节点的父节点。
     * @return { Node | Null } 如果找到符合过滤条件的节点， 就返回该节点， 否则返回NULL
     * @example
     * ```html
     * <body>
     *
     *      <div id="test">
     *      </div>
     *
     *      <script type="text/javascript">
     *
     *          //output: DIV, BODY
     *          var filterNode = UE.dom.domUtils.findParent( document.getElementById( "test" ), function ( node ) {
     *
     *              console.log( node.tagName );
     *              return false;
     *
     *          }, true );
     *
     *      </script>
     * </body>
     * ```
     */
  findParent: function(node, filterFn, includeSelf) {
    if (node && !domUtils.isBody(node)) {
      node = includeSelf ? node : node.parentNode;
      while (node) {
        if (!filterFn || filterFn(node) || domUtils.isBody(node)) {
          return filterFn && !filterFn(node) && domUtils.isBody(node)
            ? null
            : node;
        }
        node = node.parentNode;
      }
    }
    return null;
  },
  /**
     * 查找node的节点名为tagName的第一个祖先节点， 查找的起点是node节点的父节点。
     * @method findParentByTagName
     * @param { Node } node 需要查找的节点对象
     * @param { Array } tagNames 需要查找的父节点的名称数组
     * @warning 查找的终点是到body节点为止
     * @return { Node | NULL } 如果找到符合条件的节点， 则返回该节点， 否则返回NULL
     * @example
     * ```javascript
     * var node = UE.dom.domUtils.findParentByTagName( document.getElementsByTagName("div")[0], [ "BODY" ] );
     * //output: BODY
     * console.log( node.tagName );
     * ```
     */

  /**
     * 查找node的节点名为tagName的祖先节点， 如果includeSelf的值为true，则查找的起点是给定的节点node，
     * 否则， 起点是node的父节点。
     * @method findParentByTagName
     * @param { Node } node 需要查找的节点对象
     * @param { Array } tagNames 需要查找的父节点的名称数组
     * @param { Boolean } includeSelf 查找过程是否包含node节点自身
     * @warning 查找的终点是到body节点为止
     * @return { Node | NULL } 如果找到符合条件的节点， 则返回该节点， 否则返回NULL
     * @example
     * ```javascript
     * var queryTarget = document.getElementsByTagName("div")[0];
     * var node = UE.dom.domUtils.findParentByTagName( queryTarget, [ "DIV" ], true );
     * //output: true
     * console.log( queryTarget === node );
     * ```
     */
  findParentByTagName: function(node, tagNames, includeSelf, excludeFn) {
    tagNames = utils.listToMap(utils.isArray(tagNames) ? tagNames : [tagNames]);
    return domUtils.findParent(
      node,
      function(node) {
        return tagNames[node.tagName] && !(excludeFn && excludeFn(node));
      },
      includeSelf
    );
  },
  /**
     * 查找节点node的祖先节点集合， 查找的起点是给定节点的父节点，结果集中不包含给定的节点。
     * @method findParents
     * @param { Node } node 需要查找的节点对象
     * @return { Array } 给定节点的祖先节点数组
     * @grammar UE.dom.domUtils.findParents(node)  => Array  //返回一个祖先节点数组集合，不包含自身
     * @grammar UE.dom.domUtils.findParents(node,includeSelf)  => Array  //返回一个祖先节点数组集合，includeSelf指定是否包含自身
     * @grammar UE.dom.domUtils.findParents(node,includeSelf,filterFn)  => Array  //返回一个祖先节点数组集合，filterFn指定过滤条件，返回true的node将被选取
     * @grammar UE.dom.domUtils.findParents(node,includeSelf,filterFn,closerFirst)  => Array  //返回一个祖先节点数组集合，closerFirst为true的话，node的直接父亲节点是数组的第0个
     */

  /**
     * 查找节点node的祖先节点集合， 如果includeSelf的值为true，
     * 则返回的结果集中允许出现当前给定的节点， 否则， 该节点不会出现在其结果集中。
     * @method findParents
     * @param { Node } node 需要查找的节点对象
     * @param { Boolean } includeSelf 查找的结果中是否允许包含当前查找的节点对象
     * @return { Array } 给定节点的祖先节点数组
     */
  findParents: function(node, includeSelf, filterFn, closerFirst) {
    var parents = includeSelf && ((filterFn && filterFn(node)) || !filterFn)
      ? [node]
      : [];
    while ((node = domUtils.findParent(node, filterFn))) {
      parents.push(node);
    }
    return closerFirst ? parents : parents.reverse();
  },

  /**
     * 在节点node后面插入新节点newNode
     * @method insertAfter
     * @param { Node } node 目标节点
     * @param { Node } newNode 新插入的节点， 该节点将置于目标节点之后
     * @return { Node } 新插入的节点
     */
  insertAfter: function(node, newNode) {
    return node.nextSibling
      ? node.parentNode.insertBefore(newNode, node.nextSibling)
      : node.parentNode.appendChild(newNode);
  },

  /**
     * 删除节点node及其下属的所有节点
     * @method remove
     * @param { Node } node 需要删除的节点对象
     * @return { Node } 返回刚删除的节点对象
     * @example
     * ```html
     * <div id="test">
     *     <div id="child">你好</div>
     * </div>
     * <script>
     *     UE.dom.domUtils.remove( document.body, false );
     *     //output: false
     *     console.log( document.getElementById( "child" ) !== null );
     * </script>
     * ```
     */

  /**
     * 删除节点node，并根据keepChildren的值决定是否保留子节点
     * @method remove
     * @param { Node } node 需要删除的节点对象
     * @param { Boolean } keepChildren 是否需要保留子节点
     * @return { Node } 返回刚删除的节点对象
     * @example
     * ```html
     * <div id="test">
     *     <div id="child">你好</div>
     * </div>
     * <script>
     *     UE.dom.domUtils.remove( document.body, true );
     *     //output: true
     *     console.log( document.getElementById( "child" ) !== null );
     * </script>
     * ```
     */
  remove: function(node, keepChildren) {
    var parent = node.parentNode,
      child;
    if (parent) {
      if (keepChildren && node.hasChildNodes()) {
        while ((child = node.firstChild)) {
          parent.insertBefore(child, node);
        }
      }
      parent.removeChild(node);
    }
    return node;
  },

  /**
     * 取得node节点的下一个兄弟节点， 如果该节点其后没有兄弟节点， 则递归查找其父节点之后的第一个兄弟节点，
     * 直到找到满足条件的节点或者递归到BODY节点之后才会结束。
     * @method getNextDomNode
     * @param { Node } node 需要获取其后的兄弟节点的节点对象
     * @return { Node | NULL } 如果找满足条件的节点， 则返回该节点， 否则返回NULL
     * @example
     * ```html
     *     <body>
     *      <div id="test">
     *          <span></span>
     *      </div>
     *      <i>xxx</i>
     * </body>
     * <script>
     *
     *     //output: i节点
     *     console.log( UE.dom.domUtils.getNextDomNode( document.getElementById( "test" ) ) );
     *
     * </script>
     * ```
     * @example
     * ```html
     * <body>
     *      <div>
     *          <span></span>
     *          <i id="test">xxx</i>
     *      </div>
     *      <b>xxx</b>
     * </body>
     * <script>
     *
     *     //由于id为test的i节点之后没有兄弟节点， 则查找其父节点（div）后面的兄弟节点
     *     //output: b节点
     *     console.log( UE.dom.domUtils.getNextDomNode( document.getElementById( "test" ) ) );
     *
     * </script>
     * ```
     */

  /**
     * 取得node节点的下一个兄弟节点， 如果startFromChild的值为ture，则先获取其子节点，
     * 如果有子节点则直接返回第一个子节点；如果没有子节点或者startFromChild的值为false，
     * 则执行<a href="#UE.dom.domUtils.getNextDomNode(Node)">getNextDomNode(Node node)</a>的查找过程。
     * @method getNextDomNode
     * @param { Node } node 需要获取其后的兄弟节点的节点对象
     * @param { Boolean } startFromChild 查找过程是否从其子节点开始
     * @return { Node | NULL } 如果找满足条件的节点， 则返回该节点， 否则返回NULL
     * @see UE.dom.domUtils.getNextDomNode(Node)
     */
  getNextDomNode: function(node, startFromChild, filterFn, guard) {
    return getDomNode(
      node,
      "firstChild",
      "nextSibling",
      startFromChild,
      filterFn,
      guard
    );
  },
  getPreDomNode: function(node, startFromChild, filterFn, guard) {
    return getDomNode(
      node,
      "lastChild",
      "previousSibling",
      startFromChild,
      filterFn,
      guard
    );
  },
  /**
     * 检测节点node是否属是UEditor定义的bookmark节点
     * @method isBookmarkNode
     * @private
     * @param { Node } node 需要检测的节点对象
     * @return { Boolean } 是否是bookmark节点
     * @example
     * ```html
     * <span id="_baidu_bookmark_1"></span>
     * <script>
     *      var bookmarkNode = document.getElementById("_baidu_bookmark_1");
     *      //output: true
     *      console.log( UE.dom.domUtils.isBookmarkNode( bookmarkNode ) );
     * </script>
     * ```
     */
  isBookmarkNode: function(node) {
    return node.nodeType == 1 && node.id && /^_baidu_bookmark_/i.test(node.id);
  },
  /**
     * 获取节点node所属的window对象
     * @method  getWindow
     * @param { Node } node 节点对象
     * @return { Window } 当前节点所属的window对象
     * @example
     * ```javascript
     * //output: true
     * console.log( UE.dom.domUtils.getWindow( document.body ) === window );
     * ```
     */
  getWindow: function(node) {
    var doc = node.ownerDocument || node;
    return doc.defaultView || doc.parentWindow;
  },
  /**
     * 获取离nodeA与nodeB最近的公共的祖先节点
     * @method  getCommonAncestor
     * @param { Node } nodeA 第一个节点
     * @param { Node } nodeB 第二个节点
     * @remind 如果给定的两个节点是同一个节点， 将直接返回该节点。
     * @return { Node | NULL } 如果未找到公共节点， 返回NULL， 否则返回最近的公共祖先节点。
     * @example
     * ```javascript
     * var commonAncestor = UE.dom.domUtils.getCommonAncestor( document.body, document.body.firstChild );
     * //output: true
     * console.log( commonAncestor.tagName.toLowerCase() === 'body' );
     * ```
     */
  getCommonAncestor: function(nodeA, nodeB) {
    if (nodeA === nodeB) return nodeA;
    var parentsA = [nodeA],
      parentsB = [nodeB],
      parent = nodeA,
      i = -1;
    while ((parent = parent.parentNode)) {
      if (parent === nodeB) {
        return parent;
      }
      parentsA.push(parent);
    }
    parent = nodeB;
    while ((parent = parent.parentNode)) {
      if (parent === nodeA) return parent;
      parentsB.push(parent);
    }
    parentsA.reverse();
    parentsB.reverse();
    while ((i++, parentsA[i] === parentsB[i])) {}
    return i == 0 ? null : parentsA[i - 1];
  },
  /**
     * 清除node节点左右连续为空的兄弟inline节点
     * @method clearEmptySibling
     * @param { Node } node 执行的节点对象， 如果该节点的左右连续的兄弟节点是空的inline节点，
     * 则这些兄弟节点将被删除
     * @grammar UE.dom.domUtils.clearEmptySibling(node,ignoreNext)  //ignoreNext指定是否忽略右边空节点
     * @grammar UE.dom.domUtils.clearEmptySibling(node,ignoreNext,ignorePre)  //ignorePre指定是否忽略左边空节点
     * @example
     * ```html
     * <body>
     *     <div></div>
     *     <span id="test"></span>
     *     <i></i>
     *     <b></b>
     *     <em>xxx</em>
     *     <span></span>
     * </body>
     * <script>
     *
     *      UE.dom.domUtils.clearEmptySibling( document.getElementById( "test" ) );
     *
     *      //output: <div></div><span id="test"></span><em>xxx</em><span></span>
     *      console.log( document.body.innerHTML );
     *
     * </script>
     * ```
     */

  /**
     * 清除node节点左右连续为空的兄弟inline节点， 如果ignoreNext的值为true，
     * 则忽略对右边兄弟节点的操作。
     * @method clearEmptySibling
     * @param { Node } node 执行的节点对象， 如果该节点的左右连续的兄弟节点是空的inline节点，
     * @param { Boolean } ignoreNext 是否忽略忽略对右边的兄弟节点的操作
     * 则这些兄弟节点将被删除
     * @see UE.dom.domUtils.clearEmptySibling(Node)
     */

  /**
     * 清除node节点左右连续为空的兄弟inline节点， 如果ignoreNext的值为true，
     * 则忽略对右边兄弟节点的操作， 如果ignorePre的值为true，则忽略对左边兄弟节点的操作。
     * @method clearEmptySibling
     * @param { Node } node 执行的节点对象， 如果该节点的左右连续的兄弟节点是空的inline节点，
     * @param { Boolean } ignoreNext 是否忽略忽略对右边的兄弟节点的操作
     * @param { Boolean } ignorePre 是否忽略忽略对左边的兄弟节点的操作
     * 则这些兄弟节点将被删除
     * @see UE.dom.domUtils.clearEmptySibling(Node)
     */
  clearEmptySibling: function(node, ignoreNext, ignorePre) {
    function clear(next, dir) {
      var tmpNode;
      while (
        next &&
        !domUtils.isBookmarkNode(next) &&
        (domUtils.isEmptyInlineElement(next) ||
          //这里不能把空格算进来会吧空格干掉，出现文字间的空格丢掉了
          !new RegExp("[^\t\n\r" + domUtils.fillChar + "]").test(
            next.nodeValue
          ))
      ) {
        tmpNode = next[dir];
        domUtils.remove(next);
        next = tmpNode;
      }
    }
    !ignoreNext && clear(node.nextSibling, "nextSibling");
    !ignorePre && clear(node.previousSibling, "previousSibling");
  },
  /**
     * 将一个文本节点textNode拆分成两个文本节点，offset指定拆分位置
     * @method split
     * @param { Node } textNode 需要拆分的文本节点对象
     * @param { int } offset 需要拆分的位置， 位置计算从0开始
     * @return { Node } 拆分后形成的新节点
     * @example
     * ```html
     * <div id="test">abcdef</div>
     * <script>
     *      var newNode = UE.dom.domUtils.split( document.getElementById( "test" ).firstChild, 3 );
     *      //output: def
     *      console.log( newNode.nodeValue );
     * </script>
     * ```
     */
  split: function(node, offset) {
    var doc = node.ownerDocument;
    if (browser.ie && offset == node.nodeValue.length) {
      var next = doc.createTextNode("");
      return domUtils.insertAfter(node, next);
    }
    var retval = node.splitText(offset);
    //ie8下splitText不会跟新childNodes,我们手动触发他的更新
    if (browser.ie8) {
      var tmpNode = doc.createTextNode("");
      domUtils.insertAfter(retval, tmpNode);
      domUtils.remove(tmpNode);
    }
    return retval;
  },

  /**
     * 检测文本节点textNode是否为空节点（包括空格、换行、占位符等字符）
     * @method  isWhitespace
     * @param { Node } node 需要检测的节点对象
     * @return { Boolean } 检测的节点是否为空
     * @example
     * ```html
     * <div id="test">
     *
     * </div>
     * <script>
     *      //output: true
     *      console.log( UE.dom.domUtils.isWhitespace( document.getElementById("test").firstChild ) );
     * </script>
     * ```
     */
  isWhitespace: function(node) {
    return !new RegExp("[^ \t\n\r" + domUtils.fillChar + "]").test(
      node.nodeValue
    );
  },
  /**
     * 获取元素element相对于viewport的位置坐标
     * @method getXY
     * @param { Node } element 需要计算位置的节点对象
     * @return { Object } 返回形如{x:left,y:top}的一个key-value映射对象， 其中键x代表水平偏移距离，
     *                          y代表垂直偏移距离。
     *
     * @example
     * ```javascript
     * var location = UE.dom.domUtils.getXY( document.getElementById("test") );
     * //output: test的坐标为: 12, 24
     * console.log( 'test的坐标为： ', location.x, ',', location.y );
     * ```
     */
  getXY: function(element) {
    var x = 0,
      y = 0;
    while (element.offsetParent) {
      y += element.offsetTop;
      x += element.offsetLeft;
      element = element.offsetParent;
    }
    return { x: x, y: y };
  },
  /**
     * 为元素element绑定原生DOM事件，type为事件类型，handler为处理函数
     * @method on
     * @param { Node } element 需要绑定事件的节点对象
     * @param { String } type 绑定的事件类型
     * @param { Function } handler 事件处理器
     * @example
     * ```javascript
     * UE.dom.domUtils.on(document.body,"click",function(e){
     *     //e为事件对象，this为被点击元素对戏那个
     * });
     * ```
     */

  /**
     * 为元素element绑定原生DOM事件，type为事件类型，handler为处理函数
     * @method on
     * @param { Node } element 需要绑定事件的节点对象
     * @param {string} type 绑定的事件类型数组
     * @param { Function } handler 事件处理器
     * @example
     * ```javascript
     * UE.dom.domUtils.on(document.body,["click","mousedown"],function(evt){
     *     //evt为事件对象，this为被点击元素对象
     * });
     * ```
     */
  on: function(element, type, handler) {
    var types = utils.isArray(type) ? type : utils.trim(type).split(/\s+/),
      k = types.length;
    if (k)
      while (k--) {
        type = types[k];
        if (element.addEventListener) {
          element.addEventListener(type, handler, false);
        } else {
          if (!handler._d) {
            handler._d = {
              els: []
            };
          }
          var key = type + handler.toString(),
            index = utils.indexOf(handler._d.els, element);
          if (!handler._d[key] || index == -1) {
            if (index == -1) {
              handler._d.els.push(element);
            }
            if (!handler._d[key]) {
              handler._d[key] = function(evt) {
                return handler.call(evt.srcElement, evt || window.event);
              };
            }

            element.attachEvent("on" + type, handler._d[key]);
          }
        }
      }
    element = null;
  },
  /**
     * 解除DOM事件绑定
     * @method un
     * @param { Node } element 需要解除事件绑定的节点对象
     * @param { String } type 需要接触绑定的事件类型
     * @param { Function } handler 对应的事件处理器
     * @example
     * ```javascript
     * UE.dom.domUtils.un(document.body,"click",function(evt){
     *     //evt为事件对象，this为被点击元素对象
     * });
     * ```
     */

  /**
     * 解除DOM事件绑定
     * @method un
     * @param { Node } element 需要解除事件绑定的节点对象
     * @param { Array } type 需要接触绑定的事件类型数组
     * @param { Function } handler 对应的事件处理器
     * @example
     * ```javascript
     * UE.dom.domUtils.un(document.body, ["click","mousedown"],function(evt){
     *     //evt为事件对象，this为被点击元素对象
     * });
     * ```
     */
  un: function(element, type, handler) {
    var types = utils.isArray(type) ? type : utils.trim(type).split(/\s+/),
      k = types.length;
    if (k)
      while (k--) {
        type = types[k];
        if (element.removeEventListener) {
          element.removeEventListener(type, handler, false);
        } else {
          var key = type + handler.toString();
          try {
            element.detachEvent(
              "on" + type,
              handler._d ? handler._d[key] : handler
            );
          } catch (e) {}
          if (handler._d && handler._d[key]) {
            var index = utils.indexOf(handler._d.els, element);
            if (index != -1) {
              handler._d.els.splice(index, 1);
            }
            handler._d.els.length == 0 && delete handler._d[key];
          }
        }
      }
  },

  /**
     * 比较节点nodeA与节点nodeB是否具有相同的标签名、属性名以及属性值
     * @method  isSameElement
     * @param { Node } nodeA 需要比较的节点
     * @param { Node } nodeB 需要比较的节点
     * @return { Boolean } 两个节点是否具有相同的标签名、属性名以及属性值
     * @example
     * ```html
     * <span style="font-size:12px">ssss</span>
     * <span style="font-size:12px">bbbbb</span>
     * <span style="font-size:13px">ssss</span>
     * <span style="font-size:14px">bbbbb</span>
     *
     * <script>
     *
     *     var nodes = document.getElementsByTagName( "span" );
     *
     *     //output: true
     *     console.log( UE.dom.domUtils.isSameElement( nodes[0], nodes[1] ) );
     *
     *     //output: false
     *     console.log( UE.dom.domUtils.isSameElement( nodes[2], nodes[3] ) );
     *
     * </script>
     * ```
     */
  isSameElement: function(nodeA, nodeB) {
    if (nodeA.tagName != nodeB.tagName) {
      return false;
    }
    var thisAttrs = nodeA.attributes,
      otherAttrs = nodeB.attributes;
    if (!ie && thisAttrs.length != otherAttrs.length) {
      return false;
    }
    var attrA,
      attrB,
      al = 0,
      bl = 0;
    for (var i = 0; (attrA = thisAttrs[i++]); ) {
      if (attrA.nodeName == "style") {
        if (attrA.specified) {
          al++;
        }
        if (domUtils.isSameStyle(nodeA, nodeB)) {
          continue;
        } else {
          return false;
        }
      }
      if (ie) {
        if (attrA.specified) {
          al++;
          attrB = otherAttrs.getNamedItem(attrA.nodeName);
        } else {
          continue;
        }
      } else {
        attrB = nodeB.attributes[attrA.nodeName];
      }
      if (!attrB.specified || attrA.nodeValue != attrB.nodeValue) {
        return false;
      }
    }
    // 有可能attrB的属性包含了attrA的属性之外还有自己的属性
    if (ie) {
      for (i = 0; (attrB = otherAttrs[i++]); ) {
        if (attrB.specified) {
          bl++;
        }
      }
      if (al != bl) {
        return false;
      }
    }
    return true;
  },

  /**
     * 判断节点nodeA与节点nodeB的元素的style属性是否一致
     * @method isSameStyle
     * @param { Node } nodeA 需要比较的节点
     * @param { Node } nodeB 需要比较的节点
     * @return { Boolean } 两个节点是否具有相同的style属性值
     * @example
     * ```html
     * <span style="font-size:12px">ssss</span>
     * <span style="font-size:12px">bbbbb</span>
     * <span style="font-size:13px">ssss</span>
     * <span style="font-size:14px">bbbbb</span>
     *
     * <script>
     *
     *     var nodes = document.getElementsByTagName( "span" );
     *
     *     //output: true
     *     console.log( UE.dom.domUtils.isSameStyle( nodes[0], nodes[1] ) );
     *
     *     //output: false
     *     console.log( UE.dom.domUtils.isSameStyle( nodes[2], nodes[3] ) );
     *
     * </script>
     * ```
     */
  isSameStyle: function(nodeA, nodeB) {
    var styleA = nodeA.style.cssText
      .replace(/( ?; ?)/g, ";")
      .replace(/( ?: ?)/g, ":"),
      styleB = nodeB.style.cssText
        .replace(/( ?; ?)/g, ";")
        .replace(/( ?: ?)/g, ":");
    if (browser.opera) {
      styleA = nodeA.style;
      styleB = nodeB.style;
      if (styleA.length != styleB.length) return false;
      for (var p in styleA) {
        if (/^(\d+|csstext)$/i.test(p)) {
          continue;
        }
        if (styleA[p] != styleB[p]) {
          return false;
        }
      }
      return true;
    }
    if (!styleA || !styleB) {
      return styleA == styleB;
    }
    styleA = styleA.split(";");
    styleB = styleB.split(";");
    if (styleA.length != styleB.length) {
      return false;
    }
    for (var i = 0, ci; (ci = styleA[i++]); ) {
      if (utils.indexOf(styleB, ci) == -1) {
        return false;
      }
    }
    return true;
  },
  /**
     * 检查节点node是否为block元素
     * @method isBlockElm
     * @param { Node } node 需要检测的节点对象
     * @return { Boolean } 是否是block元素节点
     * @warning 该方法的判断规则如下： 如果该元素原本是block元素， 则不论该元素当前的css样式是什么都会返回true；
     *          否则，检测该元素的css样式， 如果该元素当前是block元素， 则返回true。 其余情况下都返回false。
     * @example
     * ```html
     * <span id="test1" style="display: block"></span>
     * <span id="test2"></span>
     * <div id="test3" style="display: inline"></div>
     *
     * <script>
     *
     *     //output: true
     *     console.log( UE.dom.domUtils.isBlockElm( document.getElementById("test1") ) );
     *
     *     //output: false
     *     console.log( UE.dom.domUtils.isBlockElm( document.getElementById("test2") ) );
     *
     *     //output: true
     *     console.log( UE.dom.domUtils.isBlockElm( document.getElementById("test3") ) );
     *
     * </script>
     * ```
     */
  isBlockElm: function(node) {
    return (
      node.nodeType == 1 &&
      (dtd.$block[node.tagName] ||
        styleBlock[domUtils.getComputedStyle(node, "display")]) &&
      !dtd.$nonChild[node.tagName]
    );
  },
  /**
     * 检测node节点是否为body节点
     * @method isBody
     * @param { Element } node 需要检测的dom元素
     * @return { Boolean } 给定的元素是否是body元素
     * @example
     * ```javascript
     * //output: true
     * console.log( UE.dom.domUtils.isBody( document.body ) );
     * ```
     */
  isBody: function(node) {
    return node && node.nodeType == 1 && node.tagName.toLowerCase() == "body";
  },
  /**
     * 以node节点为分界，将该节点的指定祖先节点parent拆分成两个独立的节点，
     * 拆分形成的两个节点之间是node节点
     * @method breakParent
     * @param { Node } node 作为分界的节点对象
     * @param { Node } parent 该节点必须是node节点的祖先节点， 且是block节点。
     * @return { Node } 给定的node分界节点
     * @example
     * ```javascript
     *
     *      var node = document.createElement("span"),
     *          wrapNode = document.createElement( "div" ),
     *          parent = document.createElement("p");
     *
     *      parent.appendChild( node );
     *      wrapNode.appendChild( parent );
     *
     *      //拆分前
     *      //output: <p><span></span></p>
     *      console.log( wrapNode.innerHTML );
     *
     *
     *      UE.dom.domUtils.breakParent( node, parent );
     *      //拆分后
     *      //output: <p></p><span></span><p></p>
     *      console.log( wrapNode.innerHTML );
     *
     * ```
     */
  breakParent: function(node, parent) {
    var tmpNode,
      parentClone = node,
      clone = node,
      leftNodes,
      rightNodes;
    do {
      parentClone = parentClone.parentNode;
      if (leftNodes) {
        tmpNode = parentClone.cloneNode(false);
        tmpNode.appendChild(leftNodes);
        leftNodes = tmpNode;
        tmpNode = parentClone.cloneNode(false);
        tmpNode.appendChild(rightNodes);
        rightNodes = tmpNode;
      } else {
        leftNodes = parentClone.cloneNode(false);
        rightNodes = leftNodes.cloneNode(false);
      }
      while ((tmpNode = clone.previousSibling)) {
        leftNodes.insertBefore(tmpNode, leftNodes.firstChild);
      }
      while ((tmpNode = clone.nextSibling)) {
        rightNodes.appendChild(tmpNode);
      }
      clone = parentClone;
    } while (parent !== parentClone);
    tmpNode = parent.parentNode;
    tmpNode.insertBefore(leftNodes, parent);
    tmpNode.insertBefore(rightNodes, parent);
    tmpNode.insertBefore(node, rightNodes);
    domUtils.remove(parent);
    return node;
  },
  /**
     * 检查节点node是否是空inline节点
     * @method  isEmptyInlineElement
     * @param { Node } node 需要检测的节点对象
     * @return { Number }  如果给定的节点是空的inline节点， 则返回1, 否则返回0。
     * @example
     * ```html
     * <b><i></i></b> => 1
     * <b><i></i><u></u></b> => 1
     * <b></b> => 1
     * <b>xx<i></i></b> => 0
     * ```
     */
  isEmptyInlineElement: function(node) {
    if (node.nodeType != 1 || !dtd.$removeEmpty[node.tagName]) {
      return 0;
    }
    node = node.firstChild;
    while (node) {
      //如果是创建的bookmark就跳过
      if (domUtils.isBookmarkNode(node)) {
        return 0;
      }
      if (
        (node.nodeType == 1 && !domUtils.isEmptyInlineElement(node)) ||
        (node.nodeType == 3 && !domUtils.isWhitespace(node))
      ) {
        return 0;
      }
      node = node.nextSibling;
    }
    return 1;
  },

  /**
     * 删除node节点下首尾两端的空白文本子节点
     * @method trimWhiteTextNode
     * @param { Element } node 需要执行删除操作的元素对象
     * @example
     * ```javascript
     *      var node = document.createElement("div");
     *
     *      node.appendChild( document.createTextNode( "" ) );
     *
     *      node.appendChild( document.createElement("div") );
     *
     *      node.appendChild( document.createTextNode( "" ) );
     *
     *      //3
     *      console.log( node.childNodes.length );
     *
     *      UE.dom.domUtils.trimWhiteTextNode( node );
     *
     *      //1
     *      console.log( node.childNodes.length );
     * ```
     */
  trimWhiteTextNode: function(node) {
    function remove(dir) {
      var child;
      while (
        (child = node[dir]) &&
        child.nodeType == 3 &&
        domUtils.isWhitespace(child)
      ) {
        node.removeChild(child);
      }
    }
    remove("firstChild");
    remove("lastChild");
  },

  /**
     * 合并node节点下相同的子节点
     * @name mergeChild
     * @desc
     * UE.dom.domUtils.mergeChild(node,tagName) //tagName要合并的子节点的标签
     * @example
     * <p><span style="font-size:12px;">xx<span style="font-size:12px;">aa</span>xx</span></p>
     * ==> UE.dom.domUtils.mergeChild(node,'span')
     * <p><span style="font-size:12px;">xxaaxx</span></p>
     */
  mergeChild: function(node, tagName, attrs) {
    var list = domUtils.getElementsByTagName(node, node.tagName.toLowerCase());
    for (var i = 0, ci; (ci = list[i++]); ) {
      if (!ci.parentNode || domUtils.isBookmarkNode(ci)) {
        continue;
      }
      //span单独处理
      if (ci.tagName.toLowerCase() == "span") {
        if (node === ci.parentNode) {
          domUtils.trimWhiteTextNode(node);
          if (node.childNodes.length == 1) {
            node.style.cssText = ci.style.cssText + ";" + node.style.cssText;
            domUtils.remove(ci, true);
            continue;
          }
        }
        ci.style.cssText = node.style.cssText + ";" + ci.style.cssText;
        if (attrs) {
          var style = attrs.style;
          if (style) {
            style = style.split(";");
            for (var j = 0, s; (s = style[j++]); ) {
              ci.style[utils.cssStyleToDomStyle(s.split(":")[0])] = s.split(
                ":"
              )[1];
            }
          }
        }
        if (domUtils.isSameStyle(ci, node)) {
          domUtils.remove(ci, true);
        }
        continue;
      }
      if (domUtils.isSameElement(node, ci)) {
        domUtils.remove(ci, true);
      }
    }
  },

  /**
     * 原生方法getElementsByTagName的封装
     * @method getElementsByTagName
     * @param { Node } node 目标节点对象
     * @param { String } tagName 需要查找的节点的tagName， 多个tagName以空格分割
     * @return { Array } 符合条件的节点集合
     */
  getElementsByTagName: function(node, name, filter) {
    if (filter && utils.isString(filter)) {
      var className = filter;
      filter = function(node) {
        return domUtils.hasClass(node, className);
      };
    }
    name = utils.trim(name).replace(/[ ]{2,}/g, " ").split(" ");
    var arr = [];
    for (var n = 0, ni; (ni = name[n++]); ) {
      var list = node.getElementsByTagName(ni);
      for (var i = 0, ci; (ci = list[i++]); ) {
        if (!filter || filter(ci)) arr.push(ci);
      }
    }

    return arr;
  },
  /**
     * 将节点node提取到父节点上
     * @method mergeToParent
     * @param { Element } node 需要提取的元素对象
     * @example
     * ```html
     * <div id="parent">
     *     <div id="sub">
     *         <span id="child"></span>
     *     </div>
     * </div>
     *
     * <script>
     *
     *     var child = document.getElementById( "child" );
     *
     *     //output: sub
     *     console.log( child.parentNode.id );
     *
     *     UE.dom.domUtils.mergeToParent( child );
     *
     *     //output: parent
     *     console.log( child.parentNode.id );
     *
     * </script>
     * ```
     */
  mergeToParent: function(node) {
    var parent = node.parentNode;
    while (parent && dtd.$removeEmpty[parent.tagName]) {
      if (parent.tagName == node.tagName || parent.tagName == "A") {
        //针对a标签单独处理
        domUtils.trimWhiteTextNode(parent);
        //span需要特殊处理  不处理这样的情况 <span stlye="color:#fff">xxx<span style="color:#ccc">xxx</span>xxx</span>
        if (
          (parent.tagName == "SPAN" && !domUtils.isSameStyle(parent, node)) ||
          (parent.tagName == "A" && node.tagName == "SPAN")
        ) {
          if (parent.childNodes.length > 1 || parent !== node.parentNode) {
            node.style.cssText =
              parent.style.cssText + ";" + node.style.cssText;
            parent = parent.parentNode;
            continue;
          } else {
            parent.style.cssText += ";" + node.style.cssText;
            //trace:952 a标签要保持下划线
            if (parent.tagName == "A") {
              parent.style.textDecoration = "underline";
            }
          }
        }
        if (parent.tagName != "A") {
          parent === node.parentNode && domUtils.remove(node, true);
          break;
        }
      }
      parent = parent.parentNode;
    }
  },
  /**
     * 合并节点node的左右兄弟节点
     * @method mergeSibling
     * @param { Element } node 需要合并的目标节点
     * @example
     * ```html
     * <b>xxxx</b><b id="test">ooo</b><b>xxxx</b>
     *
     * <script>
     *     var demoNode = document.getElementById("test");
     *     UE.dom.domUtils.mergeSibling( demoNode );
     *     //output: xxxxoooxxxx
     *     console.log( demoNode.innerHTML );
     * </script>
     * ```
     */

  /**
     * 合并节点node的左右兄弟节点， 可以根据给定的条件选择是否忽略合并左节点。
     * @method mergeSibling
     * @param { Element } node 需要合并的目标节点
     * @param { Boolean } ignorePre 是否忽略合并左节点
     * @example
     * ```html
     * <b>xxxx</b><b id="test">ooo</b><b>xxxx</b>
     *
     * <script>
     *     var demoNode = document.getElementById("test");
     *     UE.dom.domUtils.mergeSibling( demoNode, true );
     *     //output: oooxxxx
     *     console.log( demoNode.innerHTML );
     * </script>
     * ```
     */

  /**
     * 合并节点node的左右兄弟节点，可以根据给定的条件选择是否忽略合并左右节点。
     * @method mergeSibling
     * @param { Element } node 需要合并的目标节点
     * @param { Boolean } ignorePre 是否忽略合并左节点
     * @param { Boolean } ignoreNext 是否忽略合并右节点
     * @remind 如果同时忽略左右节点， 则该操作什么也不会做
     * @example
     * ```html
     * <b>xxxx</b><b id="test">ooo</b><b>xxxx</b>
     *
     * <script>
     *     var demoNode = document.getElementById("test");
     *     UE.dom.domUtils.mergeSibling( demoNode, false, true );
     *     //output: xxxxooo
     *     console.log( demoNode.innerHTML );
     * </script>
     * ```
     */
  mergeSibling: function(node, ignorePre, ignoreNext) {
    function merge(rtl, start, node) {
      var next;
      if (
        (next = node[rtl]) &&
        !domUtils.isBookmarkNode(next) &&
        next.nodeType == 1 &&
        domUtils.isSameElement(node, next)
      ) {
        while (next.firstChild) {
          if (start == "firstChild") {
            node.insertBefore(next.lastChild, node.firstChild);
          } else {
            node.appendChild(next.firstChild);
          }
        }
        domUtils.remove(next);
      }
    }
    !ignorePre && merge("previousSibling", "firstChild", node);
    !ignoreNext && merge("nextSibling", "lastChild", node);
  },

  /**
     * 设置节点node及其子节点不会被选中
     * @method unSelectable
     * @param { Element } node 需要执行操作的dom元素
     * @remind 执行该操作后的节点， 将不能被鼠标选中
     * @example
     * ```javascript
     * UE.dom.domUtils.unSelectable( document.body );
     * ```
     */
  unSelectable: (ie && browser.ie9below) || browser.opera
    ? function(node) {
        //for ie9
        node.onselectstart = function() {
          return false;
        };
        node.onclick = node.onkeyup = node.onkeydown = function() {
          return false;
        };
        node.unselectable = "on";
        node.setAttribute("unselectable", "on");
        for (var i = 0, ci; (ci = node.all[i++]); ) {
          switch (ci.tagName.toLowerCase()) {
            case "iframe":
            case "textarea":
            case "input":
            case "select":
              break;
            default:
              ci.unselectable = "on";
              node.setAttribute("unselectable", "on");
          }
        }
      }
    : function(node) {
        node.style.MozUserSelect = node.style.webkitUserSelect = node.style.msUserSelect = node.style.KhtmlUserSelect =
          "none";
      },
  /**
     * 删除节点node上的指定属性名称的属性
     * @method  removeAttributes
     * @param { Node } node 需要删除属性的节点对象
     * @param { String } attrNames 可以是空格隔开的多个属性名称，该操作将会依次删除相应的属性
     * @example
     * ```html
     * <div id="wrap">
     *      <span style="font-size:14px;" id="test" name="followMe">xxxxx</span>
     * </div>
     *
     * <script>
     *
     *     UE.dom.domUtils.removeAttributes( document.getElementById( "test" ), "id name" );
     *
     *     //output: <span style="font-size:14px;">xxxxx</span>
     *     console.log( document.getElementById("wrap").innerHTML );
     *
     * </script>
     * ```
     */

  /**
     * 删除节点node上的指定属性名称的属性
     * @method  removeAttributes
     * @param { Node } node 需要删除属性的节点对象
     * @param { Array } attrNames 需要删除的属性名数组
     * @example
     * ```html
     * <div id="wrap">
     *      <span style="font-size:14px;" id="test" name="followMe">xxxxx</span>
     * </div>
     *
     * <script>
     *
     *     UE.dom.domUtils.removeAttributes( document.getElementById( "test" ), ["id", "name"] );
     *
     *     //output: <span style="font-size:14px;">xxxxx</span>
     *     console.log( document.getElementById("wrap").innerHTML );
     *
     * </script>
     * ```
     */
  removeAttributes: function(node, attrNames) {
    attrNames = utils.isArray(attrNames)
      ? attrNames
      : utils.trim(attrNames).replace(/[ ]{2,}/g, " ").split(" ");
    for (var i = 0, ci; (ci = attrNames[i++]); ) {
      ci = attrFix[ci] || ci;
      switch (ci) {
        case "className":
          node[ci] = "";
          break;
        case "style":
          node.style.cssText = "";
          var val = node.getAttributeNode("style");
          !browser.ie && val && node.removeAttributeNode(val);
      }
      node.removeAttribute(ci);
    }
  },
  /**
     * 在doc下创建一个标签名为tag，属性为attrs的元素
     * @method createElement
     * @param { DomDocument } doc 新创建的元素属于该document节点创建
     * @param { String } tagName 需要创建的元素的标签名
     * @param { Object } attrs 新创建的元素的属性key-value集合
     * @return { Element } 新创建的元素对象
     * @example
     * ```javascript
     * var ele = UE.dom.domUtils.createElement( document, 'div', {
     *     id: 'test'
     * } );
     *
     * //output: DIV
     * console.log( ele.tagName );
     *
     * //output: test
     * console.log( ele.id );
     *
     * ```
     */
  createElement: function(doc, tag, attrs) {
    return domUtils.setAttributes(doc.createElement(tag), attrs);
  },
  /**
     * 为节点node添加属性attrs，attrs为属性键值对
     * @method setAttributes
     * @param { Element } node 需要设置属性的元素对象
     * @param { Object } attrs 需要设置的属性名-值对
     * @return { Element } 设置属性的元素对象
     * @example
     * ```html
     * <span id="test"></span>
     *
     * <script>
     *
     *     var testNode = UE.dom.domUtils.setAttributes( document.getElementById( "test" ), {
     *         id: 'demo'
     *     } );
     *
     *     //output: demo
     *     console.log( testNode.id );
     *
     * </script>
     *
     */
  setAttributes: function(node, attrs) {
    for (var attr in attrs) {
      if('_propertyDelete'===attr){
        for(var j=0;j<attrs[attr].length;j++){
          if(node.hasAttribute(attrs[attr][j])){
            node.removeAttribute(attrs[attr][j]);
          }
        }
        continue;
      }
      if (attrs.hasOwnProperty(attr)) {
        var value = attrs[attr];
        switch (attr) {
          case "class":
            //ie下要这样赋值，setAttribute不起作用
            node.className = value;
            break;
          case "style":
            node.style.cssText = node.style.cssText + ";" + value;
            break;
          case "innerHTML":
            node[attr] = value;
            break;
          case "value":
            node.value = value;
            break;
          default:
            node.setAttribute(attrFix[attr] || attr, value);
        }
      }
    }
    return node;
  },

  /**
     * 获取元素element经过计算后的样式值
     * @method getComputedStyle
     * @param { Element } element 需要获取样式的元素对象
     * @param { String } styleName 需要获取的样式名
     * @return { String } 获取到的样式值
     * @example
     * ```html
     * <style type="text/css">
     *      #test {
     *          font-size: 15px;
     *      }
     * </style>
     *
     * <span id="test"></span>
     *
     * <script>
     *     //output: 15px
     *     console.log( UE.dom.domUtils.getComputedStyle( document.getElementById( "test" ), 'font-size' ) );
     * </script>
     * ```
     */
  getComputedStyle: function(element, styleName) {
    //以下的属性单独处理
    var pros = "width height top left";

    if (pros.indexOf(styleName) > -1) {
      return (
        element[
          "offset" +
            styleName.replace(/^\w/, function(s) {
              return s.toUpperCase();
            })
        ] + "px"
      );
    }
    //忽略文本节点
    if (element.nodeType === 3) {
      element = element.parentNode;
    }
    //ie下font-size若body下定义了font-size，则从currentStyle里会取到这个font-size. 取不到实际值，故此修改.
    if (
      browser.ie &&
      browser.version < 9 &&
      styleName === "font-size" &&
      !element.style.fontSize &&
      !dtd.$empty[element.tagName] &&
      !dtd.$nonChild[element.tagName]
    ) {
      var span = element.ownerDocument.createElement("span");
      span.style.cssText = "padding:0;border:0;font-family:simsun;";
      span.innerHTML = ".";
      element.appendChild(span);
      var result = span.offsetHeight;
      element.removeChild(span);
      span = null;
      return result + "px";
    }
    try {
      var value =
        domUtils.getStyle(element, styleName) ||
        (window.getComputedStyle
          ? domUtils
              .getWindow(element)
              .getComputedStyle(element, "")
              .getPropertyValue(styleName)
          : (element.currentStyle || element.style)[
              utils.cssStyleToDomStyle(styleName)
            ]);
    } catch (e) {
      return "";
    }
    return utils.transUnitToPx(utils.fixColor(styleName, value));
  },
  /**
     * 删除元素element指定的className
     * @method removeClasses
     * @param { Element } ele 需要删除class的元素节点
     * @param { String } classNames 需要删除的className， 多个className之间以空格分开
     * @example
     * ```html
     * <span id="test" class="test1 test2 test3">xxx</span>
     *
     * <script>
     *
     *     var testNode = document.getElementById( "test" );
     *     UE.dom.domUtils.removeClasses( testNode, "test1 test2" );
     *
     *     //output: test3
     *     console.log( testNode.className );
     *
     * </script>
     * ```
     */

  /**
     * 删除元素element指定的className
     * @method removeClasses
     * @param { Element } ele 需要删除class的元素节点
     * @param { Array } classNames 需要删除的className数组
     * @example
     * ```html
     * <span id="test" class="test1 test2 test3">xxx</span>
     *
     * <script>
     *
     *     var testNode = document.getElementById( "test" );
     *     UE.dom.domUtils.removeClasses( testNode, ["test1", "test2"] );
     *
     *     //output: test3
     *     console.log( testNode.className );
     *
     * </script>
     * ```
     */
  removeClasses: function(elm, classNames) {
    classNames = utils.isArray(classNames)
      ? classNames
      : utils.trim(classNames).replace(/[ ]{2,}/g, " ").split(" ");
    for (var i = 0, ci, cls = elm.className; (ci = classNames[i++]); ) {
      cls = cls.replace(new RegExp("\\b" + ci + "\\b"), "");
    }
    cls = utils.trim(cls).replace(/[ ]{2,}/g, " ");
    if (cls) {
      elm.className = cls;
    } else {
      domUtils.removeAttributes(elm, ["class"]);
    }
  },
  /**
     * 给元素element添加className
     * @method addClass
     * @param { Node } ele 需要增加className的元素
     * @param { String } classNames 需要添加的className， 多个className之间以空格分割
     * @remind 相同的类名不会被重复添加
     * @example
     * ```html
     * <span id="test" class="cls1 cls2"></span>
     *
     * <script>
     *     var testNode = document.getElementById("test");
     *
     *     UE.dom.domUtils.addClass( testNode, "cls2 cls3 cls4" );
     *
     *     //output: cl1 cls2 cls3 cls4
     *     console.log( testNode.className );
     *
     * <script>
     * ```
     */

  /**
     * 给元素element添加className
     * @method addClass
     * @param { Node } ele 需要增加className的元素
     * @param { Array } classNames 需要添加的className的数组
     * @remind 相同的类名不会被重复添加
     * @example
     * ```html
     * <span id="test" class="cls1 cls2"></span>
     *
     * <script>
     *     var testNode = document.getElementById("test");
     *
     *     UE.dom.domUtils.addClass( testNode, ["cls2", "cls3", "cls4"] );
     *
     *     //output: cl1 cls2 cls3 cls4
     *     console.log( testNode.className );
     *
     * <script>
     * ```
     */
  addClass: function(elm, classNames) {
    if (!elm) return;
    classNames = utils.trim(classNames).replace(/[ ]{2,}/g, " ").split(" ");
    for (var i = 0, ci, cls = elm.className; (ci = classNames[i++]); ) {
      if (!new RegExp("\\b" + ci + "\\b").test(cls)) {
        cls += " " + ci;
      }
    }
    elm.className = utils.trim(cls);
  },
  /**
     * 判断元素element是否包含给定的样式类名className
     * @method hasClass
     * @param { Node } ele 需要检测的元素
     * @param { String } classNames 需要检测的className， 多个className之间用空格分割
     * @return { Boolean } 元素是否包含所有给定的className
     * @example
     * ```html
     * <span id="test1" class="cls1 cls2"></span>
     *
     * <script>
     *     var test1 = document.getElementById("test1");
     *
     *     //output: false
     *     console.log( UE.dom.domUtils.hasClass( test1, "cls2 cls1 cls3" ) );
     *
     *     //output: true
     *     console.log( UE.dom.domUtils.hasClass( test1, "cls2 cls1" ) );
     * </script>
     * ```
     */

  /**
     * 判断元素element是否包含给定的样式类名className
     * @method hasClass
     * @param { Node } ele 需要检测的元素
     * @param { Array } classNames 需要检测的className数组
     * @return { Boolean } 元素是否包含所有给定的className
     * @example
     * ```html
     * <span id="test1" class="cls1 cls2"></span>
     *
     * <script>
     *     var test1 = document.getElementById("test1");
     *
     *     //output: false
     *     console.log( UE.dom.domUtils.hasClass( test1, [ "cls2", "cls1", "cls3" ] ) );
     *
     *     //output: true
     *     console.log( UE.dom.domUtils.hasClass( test1, [ "cls2", "cls1" ]) );
     * </script>
     * ```
     */
  hasClass: function(element, className) {
    if (utils.isRegExp(className)) {
      return className.test(element.className);
    }
    className = utils.trim(className).replace(/[ ]{2,}/g, " ").split(" ");
    for (var i = 0, ci, cls = element.className; (ci = className[i++]); ) {
      if (!new RegExp("\\b" + ci + "\\b", "i").test(cls)) {
        return false;
      }
    }
    return i - 1 == className.length;
  },

  /**
     * 阻止事件默认行为
     * @method preventDefault
     * @param { Event } evt 需要阻止默认行为的事件对象
     * @example
     * ```javascript
     * UE.dom.domUtils.preventDefault( evt );
     * ```
     */
  preventDefault: function(evt) {
    evt.preventDefault ? evt.preventDefault() : (evt.returnValue = false);
  },
  /**
     * 删除元素element指定的样式
     * @method removeStyle
     * @param { Element } element 需要删除样式的元素
     * @param { String } styleName 需要删除的样式名
     * @example
     * ```html
     * <span id="test" style="color: red; background: blue;"></span>
     *
     * <script>
     *
     *     var testNode = document.getElementById("test");
     *
     *     UE.dom.domUtils.removeStyle( testNode, 'color' );
     *
     *     //output: background: blue;
     *     console.log( testNode.style.cssText );
     *
     * </script>
     * ```
     */
  removeStyle: function(element, name) {
    if (browser.ie) {
      //针对color先单独处理一下
      if (name == "color") {
        name = "(^|;)" + name;
      }
      element.style.cssText = element.style.cssText.replace(
        new RegExp(name + "[^:]*:[^;]+;?", "ig"),
        ""
      );
    } else {
      if (element.style.removeProperty) {
        element.style.removeProperty(name);
      } else {
        element.style.removeAttribute(utils.cssStyleToDomStyle(name));
      }
    }

    if (!element.style.cssText) {
      domUtils.removeAttributes(element, ["style"]);
    }
  },
  /**
     * 获取元素element的style属性的指定值
     * @method getStyle
     * @param { Element } element 需要获取属性值的元素
     * @param { String } styleName 需要获取的style的名称
     * @warning 该方法仅获取元素style属性中所标明的值
     * @return { String } 该元素包含指定的style属性值
     * @example
     * ```html
     * <div id="test" style="color: red;"></div>
     *
     * <script>
     *
     *      var testNode = document.getElementById( "test" );
     *
     *      //output: red
     *      console.log( UE.dom.domUtils.getStyle( testNode, "color" ) );
     *
     *      //output: ""
     *      console.log( UE.dom.domUtils.getStyle( testNode, "background" ) );
     *
     * </script>
     * ```
     */
  getStyle: function(element, name) {
    var value = element.style[utils.cssStyleToDomStyle(name)];
    return utils.fixColor(name, value);
  },
  /**
     * 为元素element设置样式属性值
     * @method setStyle
     * @param { Element } element 需要设置样式的元素
     * @param { String } styleName 样式名
     * @param { String } styleValue 样式值
     * @example
     * ```html
     * <div id="test"></div>
     *
     * <script>
     *
     *      var testNode = document.getElementById( "test" );
     *
     *      //output: ""
     *      console.log( testNode.style.color );
     *
     *      UE.dom.domUtils.setStyle( testNode, 'color', 'red' );
     *      //output: "red"
     *      console.log( testNode.style.color );
     *
     * </script>
     * ```
     */
  setStyle: function(element, name, value) {
    element.style[utils.cssStyleToDomStyle(name)] = value;
    if (!utils.trim(element.style.cssText)) {
      this.removeAttributes(element, "style");
    }
  },
  /**
     * 为元素element设置多个样式属性值
     * @method setStyles
     * @param { Element } element 需要设置样式的元素
     * @param { Object } styles 样式名值对
     * @example
     * ```html
     * <div id="test"></div>
     *
     * <script>
     *
     *      var testNode = document.getElementById( "test" );
     *
     *      //output: ""
     *      console.log( testNode.style.color );
     *
     *      UE.dom.domUtils.setStyles( testNode, {
     *          'color': 'red'
     *      } );
     *      //output: "red"
     *      console.log( testNode.style.color );
     *
     * </script>
     * ```
     */
  setStyles: function(element, styles) {
    for (var name in styles) {
      if (styles.hasOwnProperty(name)) {
        domUtils.setStyle(element, name, styles[name]);
      }
    }
  },
  /**
     * 删除_moz_dirty属性
     * @private
     * @method removeDirtyAttr
     */
  removeDirtyAttr: function(node) {
    for (
      var i = 0, ci, nodes = node.getElementsByTagName("*");
      (ci = nodes[i++]);

    ) {
      ci.removeAttribute("_moz_dirty");
    }
    node.removeAttribute("_moz_dirty");
  },
  /**
     * 获取子节点的数量
     * @method getChildCount
     * @param { Element } node 需要检测的元素
     * @return { Number } 给定的node元素的子节点数量
     * @example
     * ```html
     * <div id="test">
     *      <span></span>
     * </div>
     *
     * <script>
     *
     *     //output: 3
     *     console.log( UE.dom.domUtils.getChildCount( document.getElementById("test") ) );
     *
     * </script>
     * ```
     */

  /**
     * 根据给定的过滤规则， 获取符合条件的子节点的数量
     * @method getChildCount
     * @param { Element } node 需要检测的元素
     * @param { Function } fn 过滤器， 要求对符合条件的子节点返回true， 反之则要求返回false
     * @return { Number } 符合过滤条件的node元素的子节点数量
     * @example
     * ```html
     * <div id="test">
     *      <span></span>
     * </div>
     *
     * <script>
     *
     *     //output: 1
     *     console.log( UE.dom.domUtils.getChildCount( document.getElementById("test"), function ( node ) {
     *
     *         return node.nodeType === 1;
     *
     *     } ) );
     *
     * </script>
     * ```
     */
  getChildCount: function(node, fn) {
    var count = 0,
      first = node.firstChild;
    fn =
      fn ||
      function() {
        return 1;
      };
    while (first) {
      if (fn(first)) {
        count++;
      }
      first = first.nextSibling;
    }
    return count;
  },

  /**
     * 判断给定节点是否为空节点
     * @method isEmptyNode
     * @param { Node } node 需要检测的节点对象
     * @return { Boolean } 节点是否为空
     * @example
     * ```javascript
     * UE.dom.domUtils.isEmptyNode( document.body );
     * ```
     */
  isEmptyNode: function(node) {
    return (
      !node.firstChild ||
      domUtils.getChildCount(node, function(node) {
        return (
          !domUtils.isBr(node) &&
          !domUtils.isBookmarkNode(node) &&
          !domUtils.isWhitespace(node)
        );
      }) == 0
    );
  },
  clearSelectedArr: function(nodes) {
    var node;
    while ((node = nodes.pop())) {
      domUtils.removeAttributes(node, ["class"]);
    }
  },
  /**
     * 将显示区域滚动到指定节点的位置
     * @method scrollToView
     * @param    {Node}   node    节点
     * @param    {window}   win      window对象
     * @param    {Number}    offsetTop    距离上方的偏移量
     */
  scrollToView: function(node, win, offsetTop) {
    var getViewPaneSize = function() {
      var doc = win.document,
        mode = doc.compatMode == "CSS1Compat";
      return {
        width:
          (mode ? doc.documentElement.clientWidth : doc.body.clientWidth) || 0,
        height:
          (mode ? doc.documentElement.clientHeight : doc.body.clientHeight) || 0
      };
    },
      getScrollPosition = function(win) {
        if ("pageXOffset" in win) {
          return {
            x: win.pageXOffset || 0,
            y: win.pageYOffset || 0
          };
        } else {
          var doc = win.document;
          return {
            x: doc.documentElement.scrollLeft || doc.body.scrollLeft || 0,
            y: doc.documentElement.scrollTop || doc.body.scrollTop || 0
          };
        }
      };
    var winHeight = getViewPaneSize().height,
      offset = winHeight * -1 + offsetTop;
    offset += node.offsetHeight || 0;
    var elementPosition = domUtils.getXY(node);
    offset += elementPosition.y;
    var currentScroll = getScrollPosition(win).y;
    // offset += 50;
    if (offset > currentScroll || offset < currentScroll - winHeight) {
      win.scrollTo(0, offset + (offset < 0 ? -20 : 20));
    }
  },
  /**
     * 判断给定节点是否为br
     * @method isBr
     * @param { Node } node 需要判断的节点对象
     * @return { Boolean } 给定的节点是否是br节点
     */
  isBr: function(node) {
    return node.nodeType == 1 && node.tagName == "BR";
  },
  /**
     * 判断给定的节点是否是一个“填充”节点
     * @private
     * @method isFillChar
     * @param { Node } node 需要判断的节点
     * @param { Boolean } isInStart 是否从节点内容的开始位置匹配
     * @returns { Boolean } 节点是否是填充节点
     */
  isFillChar: function(node, isInStart) {
    if (node.nodeType != 3) return false;
    var text = node.nodeValue;
    if (isInStart) {
      return new RegExp("^" + domUtils.fillChar).test(text);
    }
    return !text.replace(new RegExp(domUtils.fillChar, "g"), "").length;
  },
  isStartInblock: function(range) {
    var tmpRange = range.cloneRange(),
      flag = 0,
      start = tmpRange.startContainer,
      tmp;
    if (start.nodeType == 1 && start.childNodes[tmpRange.startOffset]) {
      start = start.childNodes[tmpRange.startOffset];
      var pre = start.previousSibling;
      while (pre && domUtils.isFillChar(pre)) {
        start = pre;
        pre = pre.previousSibling;
      }
    }
    if (this.isFillChar(start, true) && tmpRange.startOffset == 1) {
      tmpRange.setStartBefore(start);
      start = tmpRange.startContainer;
    }

    while (start && domUtils.isFillChar(start)) {
      tmp = start;
      start = start.previousSibling;
    }
    if (tmp) {
      tmpRange.setStartBefore(tmp);
      start = tmpRange.startContainer;
    }
    if (
      start.nodeType == 1 &&
      domUtils.isEmptyNode(start) &&
      tmpRange.startOffset == 1
    ) {
      tmpRange.setStart(start, 0).collapse(true);
    }
    while (!tmpRange.startOffset) {
      start = tmpRange.startContainer;
      if (domUtils.isBlockElm(start) || domUtils.isBody(start)) {
        flag = 1;
        break;
      }
      var pre = tmpRange.startContainer.previousSibling,
        tmpNode;
      if (!pre) {
        tmpRange.setStartBefore(tmpRange.startContainer);
      } else {
        while (pre && domUtils.isFillChar(pre)) {
          tmpNode = pre;
          pre = pre.previousSibling;
        }
        if (tmpNode) {
          tmpRange.setStartBefore(tmpNode);
        } else {
          tmpRange.setStartBefore(tmpRange.startContainer);
        }
      }
    }
    return flag && !domUtils.isBody(tmpRange.startContainer) ? 1 : 0;
  },

  /**
     * 判断给定的元素是否是一个空元素
     * @method isEmptyBlock
     * @param { Element } node 需要判断的元素
     * @return { Boolean } 是否是空元素
     * @example
     * ```html
     * <div id="test"></div>
     *
     * <script>
     *     //output: true
     *     console.log( UE.dom.domUtils.isEmptyBlock( document.getElementById("test") ) );
     * </script>
     * ```
     */

  /**
     * 根据指定的判断规则判断给定的元素是否是一个空元素
     * @method isEmptyBlock
     * @param { Element } node 需要判断的元素
     * @param { RegExp } reg 对内容执行判断的正则表达式对象
     * @return { Boolean } 是否是空元素
     */
  isEmptyBlock: function(node, reg) {
    if (node.nodeType != 1) return 0;
    reg = reg || new RegExp("[ \xa0\t\r\n" + domUtils.fillChar + "]", "g");

    if (
      node[browser.ie ? "innerText" : "textContent"].replace(reg, "").length > 0
    ) {
      return 0;
    }
    for (var n in dtd.$isNotEmpty) {
      if (node.getElementsByTagName(n).length) {
        return 0;
      }
    }
    return 1;
  },

  /**
     * 移动元素使得该元素的位置移动指定的偏移量的距离
     * @method setViewportOffset
     * @param { Element } element 需要设置偏移量的元素
     * @param { Object } offset 偏移量， 形如{ left: 100, top: 50 }的一个键值对， 表示该元素将在
     *                                  现有的位置上向水平方向偏移offset.left的距离， 在竖直方向上偏移
     *                                  offset.top的距离
     * @example
     * ```html
     * <div id="test" style="top: 100px; left: 50px; position: absolute;"></div>
     *
     * <script>
     *
     *     var testNode = document.getElementById("test");
     *
     *     UE.dom.domUtils.setViewportOffset( testNode, {
     *         left: 200,
     *         top: 50
     *     } );
     *
     *     //output: top: 300px; left: 100px; position: absolute;
     *     console.log( testNode.style.cssText );
     *
     * </script>
     * ```
     */
  setViewportOffset: function(element, offset) {
    var left = parseInt(element.style.left) | 0;
    var top = parseInt(element.style.top) | 0;
    var rect = element.getBoundingClientRect();
    var offsetLeft = offset.left - rect.left;
    var offsetTop = offset.top - rect.top;
    if (offsetLeft) {
      element.style.left = left + offsetLeft + "px";
    }
    if (offsetTop) {
      element.style.top = top + offsetTop + "px";
    }
  },

  /**
     * 用“填充字符”填充节点
     * @method fillNode
     * @private
     * @param { DomDocument } doc 填充的节点所在的docment对象
     * @param { Node } node 需要填充的节点对象
     * @example
     * ```html
     * <div id="test"></div>
     *
     * <script>
     *     var testNode = document.getElementById("test");
     *
     *     //output: 0
     *     console.log( testNode.childNodes.length );
     *
     *     UE.dom.domUtils.fillNode( document, testNode );
     *
     *     //output: 1
     *     console.log( testNode.childNodes.length );
     *
     * </script>
     * ```
     */
  fillNode: function(doc, node) {
    var tmpNode = browser.ie
      ? doc.createTextNode(domUtils.fillChar)
      : doc.createElement("br");
    node.innerHTML = "";
    node.appendChild(tmpNode);
  },

  /**
     * 把节点src的所有子节点追加到另一个节点tag上去
     * @method moveChild
     * @param { Node } src 源节点， 该节点下的所有子节点将被移除
     * @param { Node } tag 目标节点， 从源节点移除的子节点将被追加到该节点下
     * @example
     * ```html
     * <div id="test1">
     *      <span></span>
     * </div>
     * <div id="test2">
     *     <div></div>
     * </div>
     *
     * <script>
     *
     *     var test1 = document.getElementById("test1"),
     *         test2 = document.getElementById("test2");
     *
     *     UE.dom.domUtils.moveChild( test1, test2 );
     *
     *     //output: ""（空字符串）
     *     console.log( test1.innerHTML );
     *
     *     //output: "<div></div><span></span>"
     *     console.log( test2.innerHTML );
     *
     * </script>
     * ```
     */

  /**
     * 把节点src的所有子节点移动到另一个节点tag上去, 可以通过dir参数控制附加的行为是“追加”还是“插入顶部”
     * @method moveChild
     * @param { Node } src 源节点， 该节点下的所有子节点将被移除
     * @param { Node } tag 目标节点， 从源节点移除的子节点将被附加到该节点下
     * @param { Boolean } dir 附加方式， 如果为true， 则附加进去的节点将被放到目标节点的顶部， 反之，则放到末尾
     * @example
     * ```html
     * <div id="test1">
     *      <span></span>
     * </div>
     * <div id="test2">
     *     <div></div>
     * </div>
     *
     * <script>
     *
     *     var test1 = document.getElementById("test1"),
     *         test2 = document.getElementById("test2");
     *
     *     UE.dom.domUtils.moveChild( test1, test2, true );
     *
     *     //output: ""（空字符串）
     *     console.log( test1.innerHTML );
     *
     *     //output: "<span></span><div></div>"
     *     console.log( test2.innerHTML );
     *
     * </script>
     * ```
     */
  moveChild: function(src, tag, dir) {
    while (src.firstChild) {
      if (dir && tag.firstChild) {
        tag.insertBefore(src.lastChild, tag.firstChild);
      } else {
        tag.appendChild(src.firstChild);
      }
    }
  },

  /**
     * 判断节点的标签上是否不存在任何属性
     * @method hasNoAttributes
     * @private
     * @param { Node } node 需要检测的节点对象
     * @return { Boolean } 节点是否不包含任何属性
     * @example
     * ```html
     * <div id="test"><span>xxxx</span></div>
     *
     * <script>
     *
     *     //output: false
     *     console.log( UE.dom.domUtils.hasNoAttributes( document.getElementById("test") ) );
     *
     *     //output: true
     *     console.log( UE.dom.domUtils.hasNoAttributes( document.getElementById("test").firstChild ) );
     *
     * </script>
     * ```
     */
  hasNoAttributes: function(node) {
    return browser.ie
      ? /^<\w+\s*?>/.test(node.outerHTML)
      : node.attributes.length == 0;
  },

  /**
     * 检测节点是否是UEditor所使用的辅助节点
     * @method isCustomeNode
     * @private
     * @param { Node } node 需要检测的节点
     * @remind 辅助节点是指编辑器要完成工作临时添加的节点， 在输出的时候将会从编辑器内移除， 不会影响最终的结果。
     * @return { Boolean } 给定的节点是否是一个辅助节点
     */
  isCustomeNode: function(node) {
    return node.nodeType == 1 && node.getAttribute("_ue_custom_node_");
  },

  /**
     * 检测节点的标签是否是给定的标签
     * @method isTagNode
     * @param { Node } node 需要检测的节点对象
     * @param { String } tagName 标签
     * @return { Boolean } 节点的标签是否是给定的标签
     * @example
     * ```html
     * <div id="test"></div>
     *
     * <script>
     *
     *     //output: true
     *     console.log( UE.dom.domUtils.isTagNode( document.getElementById("test"), "div" ) );
     *
     * </script>
     * ```
     */
  isTagNode: function(node, tagNames) {
    return (
      node.nodeType == 1 &&
      new RegExp("\\b" + node.tagName + "\\b", "i").test(tagNames)
    );
  },

  /**
     * 给定一个节点数组，在通过指定的过滤器过滤后， 获取其中满足过滤条件的第一个节点
     * @method filterNodeList
     * @param { Array } nodeList 需要过滤的节点数组
     * @param { Function } fn 过滤器， 对符合条件的节点， 执行结果返回true， 反之则返回false
     * @return { Node | NULL } 如果找到符合过滤条件的节点， 则返回该节点， 否则返回NULL
     * @example
     * ```javascript
     * var divNodes = document.getElementsByTagName("div");
     * divNodes = [].slice.call( divNodes, 0 );
     *
     * //output: null
     * console.log( UE.dom.domUtils.filterNodeList( divNodes, function ( node ) {
     *     return node.tagName.toLowerCase() !== 'div';
     * } ) );
     * ```
     */

  /**
     * 给定一个节点数组nodeList和一组标签名tagNames， 获取其中能够匹配标签名的节点集合中的第一个节点
     * @method filterNodeList
     * @param { Array } nodeList 需要过滤的节点数组
     * @param { String } tagNames 需要匹配的标签名， 多个标签名之间用空格分割
     * @return { Node | NULL } 如果找到标签名匹配的节点， 则返回该节点， 否则返回NULL
     * @example
     * ```javascript
     * var divNodes = document.getElementsByTagName("div");
     * divNodes = [].slice.call( divNodes, 0 );
     *
     * //output: null
     * console.log( UE.dom.domUtils.filterNodeList( divNodes, 'a span' ) );
     * ```
     */

  /**
     * 给定一个节点数组，在通过指定的过滤器过滤后， 如果参数forAll为true， 则会返回所有满足过滤
     * 条件的节点集合， 否则， 返回满足条件的节点集合中的第一个节点
     * @method filterNodeList
     * @param { Array } nodeList 需要过滤的节点数组
     * @param { Function } fn 过滤器， 对符合条件的节点， 执行结果返回true， 反之则返回false
     * @param { Boolean } forAll 是否返回整个节点数组, 如果该参数为false， 则返回节点集合中的第一个节点
     * @return { Array | Node | NULL } 如果找到符合过滤条件的节点， 则根据参数forAll的值决定返回满足
     *                                      过滤条件的节点数组或第一个节点， 否则返回NULL
     * @example
     * ```javascript
     * var divNodes = document.getElementsByTagName("div");
     * divNodes = [].slice.call( divNodes, 0 );
     *
     * //output: 3（假定有3个div）
     * console.log( divNodes.length );
     *
     * var nodes = UE.dom.domUtils.filterNodeList( divNodes, function ( node ) {
     *     return node.tagName.toLowerCase() === 'div';
     * }, true );
     *
     * //output: 3
     * console.log( nodes.length );
     *
     * var node = UE.dom.domUtils.filterNodeList( divNodes, function ( node ) {
     *     return node.tagName.toLowerCase() === 'div';
     * }, false );
     *
     * //output: div
     * console.log( node.nodeName );
     * ```
     */
  filterNodeList: function(nodelist, filter, forAll) {
    var results = [];
    if (!utils.isFunction(filter)) {
      var str = filter;
      filter = function(n) {
        return (
          utils.indexOf(
            utils.isArray(str) ? str : str.split(" "),
            n.tagName.toLowerCase()
          ) != -1
        );
      };
    }
    utils.each(nodelist, function(n) {
      filter(n) && results.push(n);
    });
    return results.length == 0
      ? null
      : results.length == 1 || !forAll ? results[0] : results;
  },

  /**
     * 查询给定的range选区是否在给定的node节点内，且在该节点的最末尾
     * @method isInNodeEndBoundary
     * @param { UE.dom.Range } rng 需要判断的range对象， 该对象的startContainer不能为NULL
     * @param node 需要检测的节点对象
     * @return { Number } 如果给定的选取range对象是在node内部的最末端， 则返回1, 否则返回0
     */
  isInNodeEndBoundary: function(rng, node) {
    var start = rng.startContainer;
    if (start.nodeType == 3 && rng.startOffset != start.nodeValue.length) {
      return 0;
    }
    if (start.nodeType == 1 && rng.startOffset != start.childNodes.length) {
      return 0;
    }
    while (start !== node) {
      if (start.nextSibling) {
        return 0;
      }
      start = start.parentNode;
    }
    return 1;
  },
  isBoundaryNode: function(node, dir) {
    var tmp;
    while (!domUtils.isBody(node)) {
      tmp = node;
      node = node.parentNode;
      if (tmp !== node[dir]) {
        return false;
      }
    }
    return true;
  },
  fillHtml: browser.ie11below ? "&nbsp;" : "<br/>"
});
var fillCharReg = new RegExp(domUtils.fillChar, "g");


// core/Range.js
/**
 * Range封装
 * @file
 * @module UE.dom
 * @class Range
 * @since 1.2.6.1
 */

/**
 * dom操作封装
 * @unfile
 * @module UE.dom
 */

/**
 * Range实现类，本类是UEditor底层核心类，封装不同浏览器之间的Range操作。
 * @unfile
 * @module UE.dom
 * @class Range
 */

(function() {
  var guid = 0,
    fillChar = domUtils.fillChar,
    fillData;

  /**
     * 更新range的collapse状态
     * @param  {Range}   range    range对象
     */
  function updateCollapse(range) {
    range.collapsed =
      range.startContainer &&
      range.endContainer &&
      range.startContainer === range.endContainer &&
      range.startOffset === range.endOffset;
  }

  function selectOneNode(rng) {
    return (
      !rng.collapsed &&
      rng.startContainer.nodeType === 1 &&
      rng.startContainer === rng.endContainer &&
      rng.endOffset - rng.startOffset === 1
    );
  }
  function setEndPoint(toStart, node, offset, range) {
    //如果node是自闭合标签要处理
    if (
      node.nodeType === 1 &&
      (dtd.$empty[node.tagName] || dtd.$nonChild[node.tagName])
    ) {
      offset = domUtils.getNodeIndex(node) + (toStart ? 0 : 1);
      node = node.parentNode;
    }
    if (toStart) {
      range.startContainer = node;
      range.startOffset = offset;
      if (!range.endContainer) {
        range.collapse(true);
      }
    } else {
      range.endContainer = node;
      range.endOffset = offset;
      if (!range.startContainer) {
        range.collapse(false);
      }
    }
    updateCollapse(range);
    return range;
  }

  function execContentsAction(range, action) {
    //调整边界
    //range.includeBookmark();
    var start = range.startContainer,
      end = range.endContainer,
      startOffset = range.startOffset,
      endOffset = range.endOffset,
      doc = range.document,
      frag = doc.createDocumentFragment(),
      tmpStart,
      tmpEnd;
    if (start.nodeType == 1) {
      start =
        start.childNodes[startOffset] ||
        (tmpStart = start.appendChild(doc.createTextNode("")));
    }
    if (end.nodeType == 1) {
      end =
        end.childNodes[endOffset] ||
        (tmpEnd = end.appendChild(doc.createTextNode("")));
    }
    if (start === end && start.nodeType == 3) {
      frag.appendChild(
        doc.createTextNode(
          start.substringData(startOffset, endOffset - startOffset)
        )
      );
      //is not clone
      if (action) {
        start.deleteData(startOffset, endOffset - startOffset);
        range.collapse(true);
      }
      return frag;
    }
    var current,
      currentLevel,
      clone = frag,
      startParents = domUtils.findParents(start, true),
      endParents = domUtils.findParents(end, true);
    for (var i = 0; startParents[i] == endParents[i]; ) {
      i++;
    }
    for (var j = i, si; (si = startParents[j]); j++) {
      current = si.nextSibling;
      if (si == start) {
        if (!tmpStart) {
          if (range.startContainer.nodeType == 3) {
            clone.appendChild(
              doc.createTextNode(start.nodeValue.slice(startOffset))
            );
            //is not clone
            if (action) {
              start.deleteData(
                startOffset,
                start.nodeValue.length - startOffset
              );
            }
          } else {
            clone.appendChild(!action ? start.cloneNode(true) : start);
          }
        }
      } else {
        currentLevel = si.cloneNode(false);
        clone.appendChild(currentLevel);
      }
      while (current) {
        if (current === end || current === endParents[j]) {
          break;
        }
        si = current.nextSibling;
        clone.appendChild(!action ? current.cloneNode(true) : current);
        current = si;
      }
      clone = currentLevel;
    }
    clone = frag;
    if (!startParents[i]) {
      clone.appendChild(startParents[i - 1].cloneNode(false));
      clone = clone.firstChild;
    }
    for (var j = i, ei; (ei = endParents[j]); j++) {
      current = ei.previousSibling;
      if (ei == end) {
        if (!tmpEnd && range.endContainer.nodeType == 3) {
          clone.appendChild(
            doc.createTextNode(end.substringData(0, endOffset))
          );
          //is not clone
          if (action) {
            end.deleteData(0, endOffset);
          }
        }
      } else {
        currentLevel = ei.cloneNode(false);
        clone.appendChild(currentLevel);
      }
      //如果两端同级，右边第一次已经被开始做了
      if (j != i || !startParents[i]) {
        while (current) {
          if (current === start) {
            break;
          }
          ei = current.previousSibling;
          clone.insertBefore(
            !action ? current.cloneNode(true) : current,
            clone.firstChild
          );
          current = ei;
        }
      }
      clone = currentLevel;
    }
    if (action) {
      range
        .setStartBefore(
          !endParents[i]
            ? endParents[i - 1]
            : !startParents[i] ? startParents[i - 1] : endParents[i]
        )
        .collapse(true);
    }
    tmpStart && domUtils.remove(tmpStart);
    tmpEnd && domUtils.remove(tmpEnd);
    return frag;
  }

  /**
     * 创建一个跟document绑定的空的Range实例
     * @constructor
     * @param { Document } document 新建的选区所属的文档对象
     */

  /**
     * @property { Node } startContainer 当前Range的开始边界的容器节点, 可以是一个元素节点或者是文本节点
     */

  /**
     * @property { Node } startOffset 当前Range的开始边界容器节点的偏移量, 如果是元素节点，
     *                              该值就是childNodes中的第几个节点， 如果是文本节点就是文本内容的第几个字符
     */

  /**
     * @property { Node } endContainer 当前Range的结束边界的容器节点, 可以是一个元素节点或者是文本节点
     */

  /**
     * @property { Node } endOffset 当前Range的结束边界容器节点的偏移量, 如果是元素节点，
     *                              该值就是childNodes中的第几个节点， 如果是文本节点就是文本内容的第几个字符
     */

  /**
     * @property { Boolean } collapsed 当前Range是否闭合
     * @default true
     * @remind Range是闭合的时候， startContainer === endContainer && startOffset === endOffset
     */

  /**
     * @property { Document } document 当前Range所属的Document对象
     * @remind 不同range的的document属性可以是不同的
     */
  var Range = (dom.Range = function(document) {
    var me = this;
    me.startContainer = me.startOffset = me.endContainer = me.endOffset = null;
    me.document = document;
    me.collapsed = true;
  });

  /**
     * 删除fillData
     * @param doc
     * @param excludeNode
     */
  function removeFillData(doc, excludeNode) {
    try {
      if (fillData && domUtils.inDoc(fillData, doc)) {
        if (!fillData.nodeValue.replace(fillCharReg, "").length) {
          var tmpNode = fillData.parentNode;
          domUtils.remove(fillData);
          while (
            tmpNode &&
            domUtils.isEmptyInlineElement(tmpNode) &&
            //safari的contains有bug
            (browser.safari
              ? !(
                  domUtils.getPosition(tmpNode, excludeNode) &
                  domUtils.POSITION_CONTAINS
                )
              : !tmpNode.contains(excludeNode))
          ) {
            fillData = tmpNode.parentNode;
            domUtils.remove(tmpNode);
            tmpNode = fillData;
          }
        } else {
          fillData.nodeValue = fillData.nodeValue.replace(fillCharReg, "");
        }
      }
    } catch (e) {}
  }

  /**
     * @param node
     * @param dir
     */
  function mergeSibling(node, dir) {
    var tmpNode;
    node = node[dir];
    while (node && domUtils.isFillChar(node)) {
      tmpNode = node[dir];
      domUtils.remove(node);
      node = tmpNode;
    }
  }

  Range.prototype = {
    /**
         * 克隆选区的内容到一个DocumentFragment里
         * @method cloneContents
         * @return { DocumentFragment | NULL } 如果选区是闭合的将返回null， 否则， 返回包含所clone内容的DocumentFragment元素
         * @example
         * ```html
         * <body>
         *      <!-- 中括号表示选区 -->
         *      <b>x<i>x[x</i>xx]x</b>
         *
         *      <script>
         *          //range是已选中的选区
         *          var fragment = range.cloneContents(),
         *              node = document.createElement("div");
         *
         *          node.appendChild( fragment );
         *
         *          //output: <i>x</i>xx
         *          console.log( node.innerHTML );
         *
         *      </script>
         * </body>
         * ```
         */
    cloneContents: function() {
      return this.collapsed ? null : execContentsAction(this, 0);
    },

    /**
         * 删除当前选区范围中的所有内容
         * @method deleteContents
         * @remind 执行完该操作后， 当前Range对象变成了闭合状态
         * @return { UE.dom.Range } 当前操作的Range对象
         * @example
         * ```html
         * <body>
         *      <!-- 中括号表示选区 -->
         *      <b>x<i>x[x</i>xx]x</b>
         *
         *      <script>
         *          //range是已选中的选区
         *          range.deleteContents();
         *
         *          //竖线表示闭合后的选区位置
         *          //output: <b>x<i>x</i>|x</b>
         *          console.log( document.body.innerHTML );
         *
         *          //此时， range的各项属性为
         *          //output: B
         *          console.log( range.startContainer.tagName );
         *          //output: 2
         *          console.log( range.startOffset );
         *          //output: B
         *          console.log( range.endContainer.tagName );
         *          //output: 2
         *          console.log( range.endOffset );
         *          //output: true
         *          console.log( range.collapsed );
         *
         *      </script>
         * </body>
         * ```
         */
    deleteContents: function() {
      var txt;
      if (!this.collapsed) {
        execContentsAction(this, 1);
      }
      if (browser.webkit) {
        txt = this.startContainer;
        if (txt.nodeType == 3 && !txt.nodeValue.length) {
          this.setStartBefore(txt).collapse(true);
          domUtils.remove(txt);
        }
      }
      return this;
    },

    /**
         * 将当前选区的内容提取到一个DocumentFragment里
         * @method extractContents
         * @remind 执行该操作后， 选区将变成闭合状态
         * @warning 执行该操作后， 原来选区所选中的内容将从dom树上剥离出来
         * @return { DocumentFragment } 返回包含所提取内容的DocumentFragment对象
         * @example
         * ```html
         * <body>
         *      <!-- 中括号表示选区 -->
         *      <b>x<i>x[x</i>xx]x</b>
         *
         *      <script>
         *          //range是已选中的选区
         *          var fragment = range.extractContents(),
         *              node = document.createElement( "div" );
         *
         *          node.appendChild( fragment );
         *
         *          //竖线表示闭合后的选区位置
         *
         *          //output: <b>x<i>x</i>|x</b>
         *          console.log( document.body.innerHTML );
         *          //output: <i>x</i>xx
         *          console.log( node.innerHTML );
         *
         *          //此时， range的各项属性为
         *          //output: B
         *          console.log( range.startContainer.tagName );
         *          //output: 2
         *          console.log( range.startOffset );
         *          //output: B
         *          console.log( range.endContainer.tagName );
         *          //output: 2
         *          console.log( range.endOffset );
         *          //output: true
         *          console.log( range.collapsed );
         *
         *      </script>
         * </body>
         */
    extractContents: function() {
      return this.collapsed ? null : execContentsAction(this, 2);
    },

    /**
         * 设置Range的开始容器节点和偏移量
         * @method  setStart
         * @remind 如果给定的节点是元素节点，那么offset指的是其子元素中索引为offset的元素，
         *          如果是文本节点，那么offset指的是其文本内容的第offset个字符
         * @remind 如果提供的容器节点是一个不能包含子元素的节点， 则该选区的开始容器将被设置
         *          为该节点的父节点， 此时， 其距离开始容器的偏移量也变成了该节点在其父节点
         *          中的索引
         * @param { Node } node 将被设为当前选区开始边界容器的节点对象
         * @param { int } offset 选区的开始位置偏移量
         * @return { UE.dom.Range } 当前range对象
         * @example
         * ```html
         * <!-- 选区 -->
         * <b>xxx<i>x<span>xx</span>xx<em>xx</em>xxx</i>[xxx]</b>
         *
         * <script>
         *
         *     //执行操作
         *     range.setStart( document.getElementsByTagName("i")[0], 1 );
         *
         *     //此时， 选区变成了
         *     //<b>xxx<i>x[<span>xx</span>xx<em>xx</em>xxx</i>xxx]</b>
         *
         * </script>
         * ```
         * @example
         * ```html
         * <!-- 选区 -->
         * <b>xxx<img>[xx]x</b>
         *
         * <script>
         *
         *     //执行操作
         *     range.setStart( document.getElementsByTagName("img")[0], 3 );
         *
         *     //此时， 选区变成了
         *     //<b>xxx[<img>xx]x</b>
         *
         * </script>
         * ```
         */
    setStart: function(node, offset) {
      return setEndPoint(true, node, offset, this);
    },

    /**
         * 设置Range的结束容器和偏移量
         * @method  setEnd
         * @param { Node } node 作为当前选区结束边界容器的节点对象
         * @param { int } offset 结束边界的偏移量
         * @see UE.dom.Range:setStart(Node,int)
         * @return { UE.dom.Range } 当前range对象
         */
    setEnd: function(node, offset) {
      return setEndPoint(false, node, offset, this);
    },

    /**
         * 将Range开始位置设置到node节点之后
         * @method  setStartAfter
         * @remind 该操作将会把给定节点的父节点作为range的开始容器， 且偏移量是该节点在其父节点中的位置索引+1
         * @param { Node } node 选区的开始边界将紧接着该节点之后
         * @return { UE.dom.Range } 当前range对象
         * @example
         * ```html
         * <!-- 选区示例 -->
         * <b>xx<i>xxx</i><span>xx[x</span>xxx]</b>
         *
         * <script>
         *
         *     //执行操作
         *     range.setStartAfter( document.getElementsByTagName("i")[0] );
         *
         *     //结果选区
         *     //<b>xx<i>xxx</i>[<span>xxx</span>xxx]</b>
         *
         * </script>
         * ```
         */
    setStartAfter: function(node) {
      return this.setStart(node.parentNode, domUtils.getNodeIndex(node) + 1);
    },

    /**
         * 将Range开始位置设置到node节点之前
         * @method  setStartBefore
         * @remind 该操作将会把给定节点的父节点作为range的开始容器， 且偏移量是该节点在其父节点中的位置索引
         * @param { Node } node 新的选区开始位置在该节点之前
         * @see UE.dom.Range:setStartAfter(Node)
         * @return { UE.dom.Range } 当前range对象
         */
    setStartBefore: function(node) {
      return this.setStart(node.parentNode, domUtils.getNodeIndex(node));
    },

    /**
         * 将Range结束位置设置到node节点之后
         * @method  setEndAfter
         * @remind 该操作将会把给定节点的父节点作为range的结束容器， 且偏移量是该节点在其父节点中的位置索引+1
         * @param { Node } node 目标节点
         * @see UE.dom.Range:setStartAfter(Node)
         * @return { UE.dom.Range } 当前range对象
         * @example
         * ```html
         * <!-- 选区示例 -->
         * <b>[xx<i>xxx</i><span>xx]x</span>xxx</b>
         *
         * <script>
         *
         *     //执行操作
         *     range.setStartAfter( document.getElementsByTagName("span")[0] );
         *
         *     //结果选区
         *     //<b>[xx<i>xxx</i><span>xxx</span>]xxx</b>
         *
         * </script>
         * ```
         */
    setEndAfter: function(node) {
      return this.setEnd(node.parentNode, domUtils.getNodeIndex(node) + 1);
    },

    /**
         * 将Range结束位置设置到node节点之前
         * @method  setEndBefore
         * @remind 该操作将会把给定节点的父节点作为range的结束容器， 且偏移量是该节点在其父节点中的位置索引
         * @param { Node } node 目标节点
         * @see UE.dom.Range:setEndAfter(Node)
         * @return { UE.dom.Range } 当前range对象
         */
    setEndBefore: function(node) {
      return this.setEnd(node.parentNode, domUtils.getNodeIndex(node));
    },

    /**
         * 设置Range的开始位置到node节点内的第一个子节点之前
         * @method  setStartAtFirst
         * @remind 选区的开始容器将变成给定的节点， 且偏移量为0
         * @remind 如果给定的节点是元素节点， 则该节点必须是允许包含子节点的元素。
         * @param { Node } node 目标节点
         * @see UE.dom.Range:setStartBefore(Node)
         * @return { UE.dom.Range } 当前range对象
         * @example
         * ```html
         * <!-- 选区示例 -->
         * <b>xx<i>xxx</i><span>[xx]x</span>xxx</b>
         *
         * <script>
         *
         *     //执行操作
         *     range.setStartAtFirst( document.getElementsByTagName("i")[0] );
         *
         *     //结果选区
         *     //<b>xx<i>[xxx</i><span>xx]x</span>xxx</b>
         *
         * </script>
         * ```
         */
    setStartAtFirst: function(node) {
      return this.setStart(node, 0);
    },

    /**
         * 设置Range的开始位置到node节点内的最后一个节点之后
         * @method setStartAtLast
         * @remind 选区的开始容器将变成给定的节点， 且偏移量为该节点的子节点数
         * @remind 如果给定的节点是元素节点， 则该节点必须是允许包含子节点的元素。
         * @param { Node } node 目标节点
         * @see UE.dom.Range:setStartAtFirst(Node)
         * @return { UE.dom.Range } 当前range对象
         */
    setStartAtLast: function(node) {
      return this.setStart(
        node,
        node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length
      );
    },

    /**
         * 设置Range的结束位置到node节点内的第一个节点之前
         * @method  setEndAtFirst
         * @param { Node } node 目标节点
         * @remind 选区的结束容器将变成给定的节点， 且偏移量为0
         * @remind node必须是一个元素节点， 且必须是允许包含子节点的元素。
         * @see UE.dom.Range:setStartAtFirst(Node)
         * @return { UE.dom.Range } 当前range对象
         */
    setEndAtFirst: function(node) {
      return this.setEnd(node, 0);
    },

    /**
         * 设置Range的结束位置到node节点内的最后一个节点之后
         * @method  setEndAtLast
         * @param { Node } node 目标节点
         * @remind 选区的结束容器将变成给定的节点， 且偏移量为该节点的子节点数量
         * @remind node必须是一个元素节点， 且必须是允许包含子节点的元素。
         * @see UE.dom.Range:setStartAtFirst(Node)
         * @return { UE.dom.Range } 当前range对象
         */
    setEndAtLast: function(node) {
      return this.setEnd(
        node,
        node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length
      );
    },

    /**
         * 选中给定节点
         * @method  selectNode
         * @remind 此时， 选区的开始容器和结束容器都是该节点的父节点， 其startOffset是该节点在父节点中的位置索引，
         *          而endOffset为startOffset+1
         * @param { Node } node 需要选中的节点
         * @return { UE.dom.Range } 当前range对象，此时的range仅包含当前给定的节点对象
         * @example
         * ```html
         * <!-- 选区示例 -->
         * <b>xx<i>xxx</i><span>[xx]x</span>xxx</b>
         *
         * <script>
         *
         *     //执行操作
         *     range.selectNode( document.getElementsByTagName("i")[0] );
         *
         *     //结果选区
         *     //<b>xx[<i>xxx</i>]<span>xxx</span>xxx</b>
         *
         * </script>
         * ```
         */
    selectNode: function(node) {
      return this.setStartBefore(node).setEndAfter(node);
    },

    /**
         * 选中给定节点内部的所有节点
         * @method  selectNodeContents
         * @remind 此时， 选区的开始容器和结束容器都是该节点， 其startOffset为0，
         *          而endOffset是该节点的子节点数。
         * @param { Node } node 目标节点， 当前range将包含该节点内的所有节点
         * @return { UE.dom.Range } 当前range对象， 此时range仅包含给定节点的所有子节点
         * @example
         * ```html
         * <!-- 选区示例 -->
         * <b>xx<i>xxx</i><span>[xx]x</span>xxx</b>
         *
         * <script>
         *
         *     //执行操作
         *     range.selectNode( document.getElementsByTagName("b")[0] );
         *
         *     //结果选区
         *     //<b>[xx<i>xxx</i><span>xxx</span>xxx]</b>
         *
         * </script>
         * ```
         */
    selectNodeContents: function(node) {
      return this.setStart(node, 0).setEndAtLast(node);
    },

    /**
         * clone当前Range对象
         * @method  cloneRange
         * @remind 返回的range是一个全新的range对象， 其内部所有属性与当前被clone的range相同。
         * @return { UE.dom.Range } 当前range对象的一个副本
         */
    cloneRange: function() {
      var me = this;
      return new Range(me.document)
        .setStart(me.startContainer, me.startOffset)
        .setEnd(me.endContainer, me.endOffset);
    },

    /**
         * 向当前选区的结束处闭合选区
         * @method  collapse
         * @return { UE.dom.Range } 当前range对象
         * @example
         * ```html
         * <!-- 选区示例 -->
         * <b>xx<i>xxx</i><span>[xx]x</span>xxx</b>
         *
         * <script>
         *
         *     //执行操作
         *     range.collapse();
         *
         *     //结果选区
         *     //“|”表示选区已闭合
         *     //<b>xx<i>xxx</i><span>xx|x</span>xxx</b>
         *
         * </script>
         * ```
         */

    /**
         * 闭合当前选区，根据给定的toStart参数项决定是向当前选区开始处闭合还是向结束处闭合，
         * 如果toStart的值为true，则向开始位置闭合， 反之，向结束位置闭合。
         * @method  collapse
         * @param { Boolean } toStart 是否向选区开始处闭合
         * @return { UE.dom.Range } 当前range对象，此时range对象处于闭合状态
         * @see UE.dom.Range:collapse()
         * @example
         * ```html
         * <!-- 选区示例 -->
         * <b>xx<i>xxx</i><span>[xx]x</span>xxx</b>
         *
         * <script>
         *
         *     //执行操作
         *     range.collapse( true );
         *
         *     //结果选区
         *     //“|”表示选区已闭合
         *     //<b>xx<i>xxx</i><span>|xxx</span>xxx</b>
         *
         * </script>
         * ```
         */
    collapse: function(toStart) {
      var me = this;
      if (toStart) {
        me.endContainer = me.startContainer;
        me.endOffset = me.startOffset;
      } else {
        me.startContainer = me.endContainer;
        me.startOffset = me.endOffset;
      }
      me.collapsed = true;
      return me;
    },

    /**
         * 调整range的开始位置和结束位置，使其"收缩"到最小的位置
         * @method  shrinkBoundary
         * @return { UE.dom.Range } 当前range对象
         * @example
         * ```html
         * <span>xx<b>xx[</b>xxxxx]</span> => <span>xx<b>xx</b>[xxxxx]</span>
         * ```
         *
         * @example
         * ```html
         * <!-- 选区示例 -->
         * <b>x[xx</b><i>]xxx</i>
         *
         * <script>
         *
         *     //执行收缩
         *     range.shrinkBoundary();
         *
         *     //结果选区
         *     //<b>x[xx]</b><i>xxx</i>
         * </script>
         * ```
         *
         * @example
         * ```html
         * [<b><i>xxxx</i>xxxxxxx</b>] => <b><i>[xxxx</i>xxxxxxx]</b>
         * ```
         */

    /**
         * 调整range的开始位置和结束位置，使其"收缩"到最小的位置，
         * 如果ignoreEnd的值为true，则忽略对结束位置的调整
         * @method  shrinkBoundary
         * @param { Boolean } ignoreEnd 是否忽略对结束位置的调整
         * @return { UE.dom.Range } 当前range对象
         * @see UE.dom.domUtils.Range:shrinkBoundary()
         */
    shrinkBoundary: function(ignoreEnd) {
      var me = this,
        child,
        collapsed = me.collapsed;
      function check(node) {
        return (
          node.nodeType == 1 &&
          !domUtils.isBookmarkNode(node) &&
          !dtd.$empty[node.tagName] &&
          !dtd.$nonChild[node.tagName]
        );
      }
      while (
        me.startContainer.nodeType == 1 && //是element
        (child = me.startContainer.childNodes[me.startOffset]) && //子节点也是element
        check(child)
      ) {
        me.setStart(child, 0);
      }
      if (collapsed) {
        return me.collapse(true);
      }
      if (!ignoreEnd) {
        while (
          me.endContainer.nodeType == 1 && //是element
          me.endOffset > 0 && //如果是空元素就退出 endOffset=0那么endOffst-1为负值，childNodes[endOffset]报错
          (child = me.endContainer.childNodes[me.endOffset - 1]) && //子节点也是element
          check(child)
        ) {
          me.setEnd(child, child.childNodes.length);
        }
      }
      return me;
    },

    /**
         * 获取离当前选区内包含的所有节点最近的公共祖先节点，
         * @method  getCommonAncestor
         * @remind 返回的公共祖先节点一定不是range自身的容器节点， 但有可能是一个文本节点
         * @return { Node } 当前range对象内所有节点的公共祖先节点
         * @example
         * ```html
         * //选区示例
         * <span>xxx<b>x[x<em>xx]x</em>xxx</b>xx</span>
         * <script>
         *
         *     var node = range.getCommonAncestor();
         *
         *     //公共祖先节点是： b节点
         *     //输出： B
         *     console.log(node.tagName);
         *
         * </script>
         * ```
         */

    /**
         * 获取当前选区所包含的所有节点的公共祖先节点， 可以根据给定的参数 includeSelf 决定获取到
         * 的公共祖先节点是否可以是当前选区的startContainer或endContainer节点， 如果 includeSelf
         * 的取值为true， 则返回的节点可以是自身的容器节点， 否则， 则不能是容器节点
         * @method  getCommonAncestor
         * @param { Boolean } includeSelf 是否允许获取到的公共祖先节点是当前range对象的容器节点
         * @return { Node } 当前range对象内所有节点的公共祖先节点
         * @see UE.dom.Range:getCommonAncestor()
         * @example
         * ```html
         * <body>
         *
         *     <!-- 选区示例 -->
         *     <b>xxx<i>xxxx<span>xx[x</span>xx]x</i>xxxxxxx</b>
         *
         *     <script>
         *
         *         var node = range.getCommonAncestor( false );
         *
         *         //这里的公共祖先节点是B而不是I， 是因为参数限制了获取到的节点不能是容器节点
         *         //output: B
         *         console.log( node.tagName );
         *
         *     </script>
         *
         * </body>
         * ```
         */

    /**
         * 获取当前选区所包含的所有节点的公共祖先节点， 可以根据给定的参数 includeSelf 决定获取到
         * 的公共祖先节点是否可以是当前选区的startContainer或endContainer节点， 如果 includeSelf
         * 的取值为true， 则返回的节点可以是自身的容器节点， 否则， 则不能是容器节点； 同时可以根据
         * ignoreTextNode 参数的取值决定是否忽略类型为文本节点的祖先节点。
         * @method  getCommonAncestor
         * @param { Boolean } includeSelf 是否允许获取到的公共祖先节点是当前range对象的容器节点
         * @param { Boolean } ignoreTextNode 获取祖先节点的过程中是否忽略类型为文本节点的祖先节点
         * @return { Node } 当前range对象内所有节点的公共祖先节点
         * @see UE.dom.Range:getCommonAncestor()
         * @see UE.dom.Range:getCommonAncestor(Boolean)
         * @example
         * ```html
         * <body>
         *
         *     <!-- 选区示例 -->
         *     <b>xxx<i>xxxx<span>x[x]x</span>xxx</i>xxxxxxx</b>
         *
         *     <script>
         *
         *         var node = range.getCommonAncestor( true, false );
         *
         *         //output: SPAN
         *         console.log( node.tagName );
         *
         *     </script>
         *
         * </body>
         * ```
         */
    getCommonAncestor: function(includeSelf, ignoreTextNode) {
      var me = this,
        start = me.startContainer,
        end = me.endContainer;
      if (start === end) {
        if (includeSelf && selectOneNode(this)) {
          start = start.childNodes[me.startOffset];
          if (start.nodeType == 1) return start;
        }
        //只有在上来就相等的情况下才会出现是文本的情况
        return ignoreTextNode && start.nodeType == 3 ? start.parentNode : start;
      }
      return domUtils.getCommonAncestor(start, end);
    },

    /**
         * 调整当前Range的开始和结束边界容器，如果是容器节点是文本节点,就调整到包含该文本节点的父节点上
         * @method trimBoundary
         * @remind 该操作有可能会引起文本节点被切开
         * @return { UE.dom.Range } 当前range对象
         * @example
         * ```html
         *
         * //选区示例
         * <b>xxx<i>[xxxxx]</i>xxx</b>
         *
         * <script>
         *     //未调整前， 选区的开始容器和结束都是文本节点
         *     //执行调整
         *     range.trimBoundary();
         *
         *     //调整之后， 容器节点变成了i节点
         *     //<b>xxx[<i>xxxxx</i>]xxx</b>
         * </script>
         * ```
         */

    /**
         * 调整当前Range的开始和结束边界容器，如果是容器节点是文本节点,就调整到包含该文本节点的父节点上，
         * 可以根据 ignoreEnd 参数的值决定是否调整对结束边界的调整
         * @method trimBoundary
         * @param { Boolean } ignoreEnd 是否忽略对结束边界的调整
         * @return { UE.dom.Range } 当前range对象
         * @example
         * ```html
         *
         * //选区示例
         * <b>xxx<i>[xxxxx]</i>xxx</b>
         *
         * <script>
         *     //未调整前， 选区的开始容器和结束都是文本节点
         *     //执行调整
         *     range.trimBoundary( true );
         *
         *     //调整之后， 开始容器节点变成了i节点
         *     //但是， 结束容器没有发生变化
         *     //<b>xxx[<i>xxxxx]</i>xxx</b>
         * </script>
         * ```
         */
    trimBoundary: function(ignoreEnd) {
      this.txtToElmBoundary();
      var start = this.startContainer,
        offset = this.startOffset,
        collapsed = this.collapsed,
        end = this.endContainer;
      if (start.nodeType == 3) {
        if (offset == 0) {
          this.setStartBefore(start);
        } else {
          if (offset >= start.nodeValue.length) {
            this.setStartAfter(start);
          } else {
            var textNode = domUtils.split(start, offset);
            //跟新结束边界
            if (start === end) {
              this.setEnd(textNode, this.endOffset - offset);
            } else if (start.parentNode === end) {
              this.endOffset += 1;
            }
            this.setStartBefore(textNode);
          }
        }
        if (collapsed) {
          return this.collapse(true);
        }
      }
      if (!ignoreEnd) {
        offset = this.endOffset;
        end = this.endContainer;
        if (end.nodeType == 3) {
          if (offset == 0) {
            this.setEndBefore(end);
          } else {
            offset < end.nodeValue.length && domUtils.split(end, offset);
            this.setEndAfter(end);
          }
        }
      }
      return this;
    },

    /**
         * 如果选区在文本的边界上，就扩展选区到文本的父节点上, 如果当前选区是闭合的， 则什么也不做
         * @method txtToElmBoundary
         * @remind 该操作不会修改dom节点
         * @return { UE.dom.Range } 当前range对象
         */

    /**
         * 如果选区在文本的边界上，就扩展选区到文本的父节点上, 如果当前选区是闭合的， 则根据参数项
         * ignoreCollapsed 的值决定是否执行该调整
         * @method txtToElmBoundary
         * @param { Boolean } ignoreCollapsed 是否忽略选区的闭合状态， 如果该参数取值为true， 则
         *                      不论选区是否闭合， 都会执行该操作， 反之， 则不会对闭合的选区执行该操作
         * @return { UE.dom.Range } 当前range对象
         */
    txtToElmBoundary: function(ignoreCollapsed) {
      function adjust(r, c) {
        var container = r[c + "Container"],
          offset = r[c + "Offset"];
        if (container.nodeType == 3) {
          if (!offset) {
            r[
              "set" +
                c.replace(/(\w)/, function(a) {
                  return a.toUpperCase();
                }) +
                "Before"
            ](container);
          } else if (offset >= container.nodeValue.length) {
            r[
              "set" +
                c.replace(/(\w)/, function(a) {
                  return a.toUpperCase();
                }) +
                "After"
            ](container);
          }
        }
      }

      if (ignoreCollapsed || !this.collapsed) {
        adjust(this, "start");
        adjust(this, "end");
      }
      return this;
    },

    /**
         * 在当前选区的开始位置前插入节点，新插入的节点会被该range包含
         * @method  insertNode
         * @param { Node } node 需要插入的节点
         * @remind 插入的节点可以是一个DocumentFragment依次插入多个节点
         * @return { UE.dom.Range } 当前range对象
         */
    insertNode: function(node) {
      var first = node,
        length = 1;
      if (node.nodeType == 11) {
        first = node.firstChild;
        length = node.childNodes.length;
      }
      this.trimBoundary(true);
      var start = this.startContainer,
        offset = this.startOffset;
      var nextNode = start.childNodes[offset];
      if (nextNode) {
        start.insertBefore(node, nextNode);
      } else {
        start.appendChild(node);
      }
      if (first.parentNode === this.endContainer) {
        this.endOffset = this.endOffset + length;
      }
      return this.setStartBefore(first);
    },

    /**
         * 闭合选区到当前选区的开始位置， 并且定位光标到闭合后的位置
         * @method  setCursor
         * @return { UE.dom.Range } 当前range对象
         * @see UE.dom.Range:collapse()
         */

    /**
         * 闭合选区，可以根据参数toEnd的值控制选区是向前闭合还是向后闭合， 并且定位光标到闭合后的位置。
         * @method  setCursor
         * @param { Boolean } toEnd 是否向后闭合， 如果为true， 则闭合选区时， 将向结束容器方向闭合，
         *                      反之，则向开始容器方向闭合
         * @return { UE.dom.Range } 当前range对象
         * @see UE.dom.Range:collapse(Boolean)
         */
    setCursor: function(toEnd, noFillData) {
      return this.collapse(!toEnd).select(noFillData);
    },

    /**
         * 创建当前range的一个书签，记录下当前range的位置，方便当dom树改变时，还能找回原来的选区位置
         * @method createBookmark
         * @param { Boolean } serialize 控制返回的标记位置是对当前位置的引用还是ID，如果该值为true，则
         *                              返回标记位置的ID， 反之则返回标记位置节点的引用
         * @return { Object } 返回一个书签记录键值对， 其包含的key有： start => 开始标记的ID或者引用，
         *                          end => 结束标记的ID或引用， id => 当前标记的类型， 如果为true，则表示
         *                          返回的记录的类型为ID， 反之则为引用
         */
    createBookmark: function(serialize, same) {
      var endNode,
        startNode = this.document.createElement("span");
      startNode.style.cssText = "display:none;line-height:0px;";
      startNode.appendChild(this.document.createTextNode("\u200D"));
      startNode.id = "_baidu_bookmark_start_" + (same ? "" : guid++);

      if (!this.collapsed) {
        endNode = startNode.cloneNode(true);
        endNode.id = "_baidu_bookmark_end_" + (same ? "" : guid++);
      }
      this.insertNode(startNode);
      if (endNode) {
        this.collapse().insertNode(endNode).setEndBefore(endNode);
      }
      this.setStartAfter(startNode);
      return {
        start: serialize ? startNode.id : startNode,
        end: endNode ? (serialize ? endNode.id : endNode) : null,
        id: serialize
      };
    },

    /**
         *  调整当前range的边界到书签位置，并删除该书签对象所标记的位置内的节点
         *  @method  moveToBookmark
         *  @param { BookMark } bookmark createBookmark所创建的标签对象
         *  @return { UE.dom.Range } 当前range对象
         *  @see UE.dom.Range:createBookmark(Boolean)
         */
    moveToBookmark: function(bookmark) {
      var start = bookmark.id
        ? this.document.getElementById(bookmark.start)
        : bookmark.start,
        end = bookmark.end && bookmark.id
          ? this.document.getElementById(bookmark.end)
          : bookmark.end;
      this.setStartBefore(start);
      domUtils.remove(start);
      if (end) {
        this.setEndBefore(end);
        domUtils.remove(end);
      } else {
        this.collapse(true);
      }
      return this;
    },

    /**
         * 调整range的边界，使其"放大"到最近的父节点
         * @method  enlarge
         * @remind 会引起选区的变化
         * @return { UE.dom.Range } 当前range对象
         */

    /**
         * 调整range的边界，使其"放大"到最近的父节点，根据参数 toBlock 的取值， 可以
         * 要求扩大之后的父节点是block节点
         * @method  enlarge
         * @param { Boolean } toBlock 是否要求扩大之后的父节点必须是block节点
         * @return { UE.dom.Range } 当前range对象
         */
    enlarge: function(toBlock, stopFn) {
      var isBody = domUtils.isBody,
        pre,
        node,
        tmp = this.document.createTextNode("");
      if (toBlock) {
        node = this.startContainer;
        if (node.nodeType == 1) {
          if (node.childNodes[this.startOffset]) {
            pre = node = node.childNodes[this.startOffset];
          } else {
            node.appendChild(tmp);
            pre = node = tmp;
          }
        } else {
          pre = node;
        }
        while (1) {
          if (domUtils.isBlockElm(node)) {
            node = pre;
            while ((pre = node.previousSibling) && !domUtils.isBlockElm(pre)) {
              node = pre;
            }
            this.setStartBefore(node);
            break;
          }
          pre = node;
          node = node.parentNode;
        }
        node = this.endContainer;
        if (node.nodeType == 1) {
          if ((pre = node.childNodes[this.endOffset])) {
            node.insertBefore(tmp, pre);
          } else {
            node.appendChild(tmp);
          }
          pre = node = tmp;
        } else {
          pre = node;
        }
        while (1) {
          if (domUtils.isBlockElm(node)) {
            node = pre;
            while ((pre = node.nextSibling) && !domUtils.isBlockElm(pre)) {
              node = pre;
            }
            this.setEndAfter(node);
            break;
          }
          pre = node;
          node = node.parentNode;
        }
        if (tmp.parentNode === this.endContainer) {
          this.endOffset--;
        }
        domUtils.remove(tmp);
      }

      // 扩展边界到最大
      if (!this.collapsed) {
        while (this.startOffset == 0) {
          if (stopFn && stopFn(this.startContainer)) {
            break;
          }
          if (isBody(this.startContainer)) {
            break;
          }
          this.setStartBefore(this.startContainer);
        }
        while (
          this.endOffset ==
          (this.endContainer.nodeType == 1
            ? this.endContainer.childNodes.length
            : this.endContainer.nodeValue.length)
        ) {
          if (stopFn && stopFn(this.endContainer)) {
            break;
          }
          if (isBody(this.endContainer)) {
            break;
          }
          this.setEndAfter(this.endContainer);
        }
      }
      return this;
    },
    enlargeToBlockElm: function(ignoreEnd) {
      while (!domUtils.isBlockElm(this.startContainer)) {
        this.setStartBefore(this.startContainer);
      }
      if (!ignoreEnd) {
        while (!domUtils.isBlockElm(this.endContainer)) {
          this.setEndAfter(this.endContainer);
        }
      }
      return this;
    },
    /**
         * 调整Range的边界，使其"缩小"到最合适的位置
         * @method adjustmentBoundary
         * @return { UE.dom.Range } 当前range对象
         * @see UE.dom.Range:shrinkBoundary()
         */
    adjustmentBoundary: function() {
      if (!this.collapsed) {
        while (
          !domUtils.isBody(this.startContainer) &&
          this.startOffset ==
            this.startContainer[
              this.startContainer.nodeType == 3 ? "nodeValue" : "childNodes"
            ].length &&
          this.startContainer[
            this.startContainer.nodeType == 3 ? "nodeValue" : "childNodes"
          ].length
        ) {
          this.setStartAfter(this.startContainer);
        }
        while (
          !domUtils.isBody(this.endContainer) &&
          !this.endOffset &&
          this.endContainer[
            this.endContainer.nodeType == 3 ? "nodeValue" : "childNodes"
          ].length
        ) {
          this.setEndBefore(this.endContainer);
        }
      }
      return this;
    },

    /**
         * 给range选区中的内容添加给定的inline标签
         * @method applyInlineStyle
         * @param { String } tagName 需要添加的标签名
         * @example
         * ```html
         * <p>xxxx[xxxx]x</p>  ==>  range.applyInlineStyle("strong")  ==>  <p>xxxx[<strong>xxxx</strong>]x</p>
         * ```
         */

    /**
         * 给range选区中的内容添加给定的inline标签， 并且为标签附加上一些初始化属性。
         * @method applyInlineStyle
         * @param { String } tagName 需要添加的标签名
         * @param { Object } attrs 跟随新添加的标签的属性
         * @return { UE.dom.Range } 当前选区
         * @example
         * ```html
         * <p>xxxx[xxxx]x</p>
         *
         * ==>
         *
         * <!-- 执行操作 -->
         * range.applyInlineStyle("strong",{"style":"font-size:12px"})
         *
         * ==>
         *
         * <p>xxxx[<strong style="font-size:12px">xxxx</strong>]x</p>
         * ```
         */
    applyInlineStyle: function(tagName, attrs, list) {
      if (this.collapsed) return this;
      this.trimBoundary()
        .enlarge(false, function(node) {
          return node.nodeType == 1 && domUtils.isBlockElm(node);
        })
        .adjustmentBoundary();
      var bookmark = this.createBookmark(),
        end = bookmark.end,
        filterFn = function(node) {
          return node.nodeType == 1
            ? node.tagName.toLowerCase() != "br"
            : !domUtils.isWhitespace(node);
        },
        current = domUtils.getNextDomNode(bookmark.start, false, filterFn),
        node,
        pre,
        range = this.cloneRange();
      while (
        current &&
        domUtils.getPosition(current, end) & domUtils.POSITION_PRECEDING
      ) {
        if (current.nodeType == 3 || dtd[tagName][current.tagName]) {
          range.setStartBefore(current);
          node = current;
          while (
            node &&
            (node.nodeType == 3 || dtd[tagName][node.tagName]) &&
            node !== end
          ) {
            pre = node;
            node = domUtils.getNextDomNode(
              node,
              node.nodeType == 1,
              null,
              function(parent) {
                return dtd[tagName][parent.tagName];
              }
            );
          }
          var frag = range.setEndAfter(pre).extractContents(),
            elm;
          if (list && list.length > 0) {
            var level, top;
            top = level = list[0].cloneNode(false);
            for (var i = 1, ci; (ci = list[i++]); ) {
              level.appendChild(ci.cloneNode(false));
              level = level.firstChild;
            }
            elm = level;
          } else {
            elm = range.document.createElement(tagName);
          }
          if (attrs) {
            domUtils.setAttributes(elm, attrs);
          }
          elm.appendChild(frag);
          //针对嵌套span的全局样式指定，做容错处理
          if (elm.tagName == "SPAN" && attrs && attrs.style) {
            utils.each(elm.getElementsByTagName("span"), function(s) {
              s.style.cssText = s.style.cssText + ";" + attrs.style;
            });
          }
          range.insertNode(list ? top : elm);
          //处理下滑线在a上的情况
          var aNode;
          if (
            tagName == "span" &&
            attrs.style &&
            /text\-decoration/.test(attrs.style) &&
            (aNode = domUtils.findParentByTagName(elm, "a", true))
          ) {
            domUtils.setAttributes(aNode, attrs);
            domUtils.remove(elm, true);
            elm = aNode;
          } else {
            domUtils.mergeSibling(elm);
            domUtils.clearEmptySibling(elm);
          }
          //去除子节点相同的
          domUtils.mergeChild(elm, attrs);
          current = domUtils.getNextDomNode(elm, false, filterFn);
          domUtils.mergeToParent(elm);
          if (node === end) {
            break;
          }
        } else {
          current = domUtils.getNextDomNode(current, true, filterFn);
        }
      }
      return this.moveToBookmark(bookmark);
    },

    /**
         * 移除当前选区内指定的inline标签，但保留其中的内容
         * @method removeInlineStyle
         * @param { String } tagName 需要移除的标签名
         * @return { UE.dom.Range } 当前的range对象
         * @example
         * ```html
         * xx[x<span>xxx<em>yyy</em>zz]z</span>  => range.removeInlineStyle(["em"])  => xx[x<span>xxxyyyzz]z</span>
         * ```
         */

    /**
         * 移除当前选区内指定的一组inline标签，但保留其中的内容
         * @method removeInlineStyle
         * @param { Array } tagNameArr 需要移除的标签名的数组
         * @return { UE.dom.Range } 当前的range对象
         * @see UE.dom.Range:removeInlineStyle(String)
         */
    removeInlineStyle: function(tagNames) {
      if (this.collapsed) return this;
      tagNames = utils.isArray(tagNames) ? tagNames : [tagNames];
      this.shrinkBoundary().adjustmentBoundary();
      var start = this.startContainer,
        end = this.endContainer;
      while (1) {
        if (start.nodeType == 1) {
          if (utils.indexOf(tagNames, start.tagName.toLowerCase()) > -1) {
            break;
          }
          if (start.tagName.toLowerCase() == "body") {
            start = null;
            break;
          }
        }
        start = start.parentNode;
      }
      while (1) {
        if (end.nodeType == 1) {
          if (utils.indexOf(tagNames, end.tagName.toLowerCase()) > -1) {
            break;
          }
          if (end.tagName.toLowerCase() == "body") {
            end = null;
            break;
          }
        }
        end = end.parentNode;
      }
      var bookmark = this.createBookmark(),
        frag,
        tmpRange;
      if (start) {
        tmpRange = this.cloneRange()
          .setEndBefore(bookmark.start)
          .setStartBefore(start);
        frag = tmpRange.extractContents();
        tmpRange.insertNode(frag);
        domUtils.clearEmptySibling(start, true);
        start.parentNode.insertBefore(bookmark.start, start);
      }
      if (end) {
        tmpRange = this.cloneRange()
          .setStartAfter(bookmark.end)
          .setEndAfter(end);
        frag = tmpRange.extractContents();
        tmpRange.insertNode(frag);
        domUtils.clearEmptySibling(end, false, true);
        end.parentNode.insertBefore(bookmark.end, end.nextSibling);
      }
      var current = domUtils.getNextDomNode(bookmark.start, false, function(
        node
      ) {
        return node.nodeType == 1;
      }),
        next;
      while (current && current !== bookmark.end) {
        next = domUtils.getNextDomNode(current, true, function(node) {
          return node.nodeType == 1;
        });
        if (utils.indexOf(tagNames, current.tagName.toLowerCase()) > -1) {
          domUtils.remove(current, true);
        }
        current = next;
      }
      return this.moveToBookmark(bookmark);
    },

    /**
         * 获取当前选中的自闭合的节点
         * @method  getClosedNode
         * @return { Node | NULL } 如果当前选中的是自闭合节点， 则返回该节点， 否则返回NULL
         */
    getClosedNode: function() {
      var node;
      if (!this.collapsed) {
        var range = this.cloneRange().adjustmentBoundary().shrinkBoundary();
        if (selectOneNode(range)) {
          var child = range.startContainer.childNodes[range.startOffset];
          if (
            child &&
            child.nodeType === 1 &&
            (dtd.$empty[child.tagName] || dtd.$nonChild[child.tagName])
          ) {
            node = child;
          }
        }
      }
      return node;
    },

    /**
         * 在页面上高亮range所表示的选区
         * @method select
         * @return { UE.dom.Range } 返回当前Range对象
         */
    //这里不区分ie9以上，trace:3824
    select: browser.ie
      ? function(noFillData, textRange) {
          var nativeRange;
          if (!this.collapsed) this.shrinkBoundary();
          var node = this.getClosedNode();
          if (node && !textRange) {
            try {
              nativeRange = this.document.body.createControlRange();
              nativeRange.addElement(node);
              nativeRange.select();
            } catch (e) {}
            return this;
          }
          var bookmark = this.createBookmark(),
            start = bookmark.start,
            end;
          nativeRange = this.document.body.createTextRange();
          nativeRange.moveToElementText(start);
          nativeRange.moveStart("character", 1);
          if (!this.collapsed) {
            var nativeRangeEnd = this.document.body.createTextRange();
            end = bookmark.end;
            nativeRangeEnd.moveToElementText(end);
            nativeRange.setEndPoint("EndToEnd", nativeRangeEnd);
          } else {
            if (!noFillData && this.startContainer.nodeType != 3) {
              //使用<span>|x<span>固定住光标
              var tmpText = this.document.createTextNode(fillChar),
                tmp = this.document.createElement("span");
              tmp.appendChild(this.document.createTextNode(fillChar));
              start.parentNode.insertBefore(tmp, start);
              start.parentNode.insertBefore(tmpText, start);
              //当点b,i,u时，不能清除i上边的b
              removeFillData(this.document, tmpText);
              fillData = tmpText;
              mergeSibling(tmp, "previousSibling");
              mergeSibling(start, "nextSibling");
              nativeRange.moveStart("character", -1);
              nativeRange.collapse(true);
            }
          }
          this.moveToBookmark(bookmark);
          tmp && domUtils.remove(tmp);
          //IE在隐藏状态下不支持range操作，catch一下
          try {
            nativeRange.select();
          } catch (e) {}
          return this;
        }
      : function(notInsertFillData) {
          function checkOffset(rng) {
            function check(node, offset, dir) {
              if (node.nodeType == 3 && node.nodeValue.length < offset) {
                rng[dir + "Offset"] = node.nodeValue.length;
              }
            }
            check(rng.startContainer, rng.startOffset, "start");
            check(rng.endContainer, rng.endOffset, "end");
          }
          var win = domUtils.getWindow(this.document),
            sel = win.getSelection(),
            txtNode;
          //FF下关闭自动长高时滚动条在关闭dialog时会跳
          //ff下如果不body.focus将不能定位闭合光标到编辑器内
          browser.gecko ? this.document.body.focus() : win.focus();
          if (sel) {
            sel.removeAllRanges();
            // trace:870 chrome/safari后边是br对于闭合得range不能定位 所以去掉了判断
            // this.startContainer.nodeType != 3 &&! ((child = this.startContainer.childNodes[this.startOffset]) && child.nodeType == 1 && child.tagName == 'BR'
            if (this.collapsed && !notInsertFillData) {
              //                    //opear如果没有节点接着，原生的不能够定位,不能在body的第一级插入空白节点
              //                    if (notInsertFillData && browser.opera && !domUtils.isBody(this.startContainer) && this.startContainer.nodeType == 1) {
              //                        var tmp = this.document.createTextNode('');
              //                        this.insertNode(tmp).setStart(tmp, 0).collapse(true);
              //                    }
              //
              //处理光标落在文本节点的情况
              //处理以下的情况
              //<b>|xxxx</b>
              //<b>xxxx</b>|xxxx
              //xxxx<b>|</b>
              var start = this.startContainer,
                child = start;
              if (start.nodeType == 1) {
                child = start.childNodes[this.startOffset];
              }
              if (
                !(start.nodeType == 3 && this.startOffset) &&
                (child
                  ? !child.previousSibling ||
                      child.previousSibling.nodeType != 3
                  : !start.lastChild || start.lastChild.nodeType != 3)
              ) {
                txtNode = this.document.createTextNode(fillChar);
                //跟着前边走
                this.insertNode(txtNode);
                removeFillData(this.document, txtNode);
                mergeSibling(txtNode, "previousSibling");
                mergeSibling(txtNode, "nextSibling");
                fillData = txtNode;
                this.setStart(txtNode, browser.webkit ? 1 : 0).collapse(true);
              }
            }
            var nativeRange = this.document.createRange();
            if (
              this.collapsed &&
              browser.opera &&
              this.startContainer.nodeType == 1
            ) {
              var child = this.startContainer.childNodes[this.startOffset];
              if (!child) {
                //往前靠拢
                child = this.startContainer.lastChild;
                if (child && domUtils.isBr(child)) {
                  this.setStartBefore(child).collapse(true);
                }
              } else {
                //向后靠拢
                while (child && domUtils.isBlockElm(child)) {
                  if (child.nodeType == 1 && child.childNodes[0]) {
                    child = child.childNodes[0];
                  } else {
                    break;
                  }
                }
                child && this.setStartBefore(child).collapse(true);
              }
            }
            //是createAddress最后一位算的不准，现在这里进行微调
            checkOffset(this);
            nativeRange.setStart(this.startContainer, this.startOffset);
            nativeRange.setEnd(this.endContainer, this.endOffset);
            sel.addRange(nativeRange);
          }
          return this;
        },

    /**
         * 滚动到当前range开始的位置
         * @method scrollToView
         * @param { Window } win 当前range对象所属的window对象
         * @return { UE.dom.Range } 当前Range对象
         */

    /**
         * 滚动到距离当前range开始位置 offset 的位置处
         * @method scrollToView
         * @param { Window } win 当前range对象所属的window对象
         * @param { Number } offset 距离range开始位置处的偏移量， 如果为正数， 则向下偏移， 反之， 则向上偏移
         * @return { UE.dom.Range } 当前Range对象
         */
    scrollToView: function(win, offset) {
      win = win ? window : domUtils.getWindow(this.document);
      var me = this,
        span = me.document.createElement("span");
      //trace:717
      span.innerHTML = "&nbsp;";
      me.cloneRange().insertNode(span);
      domUtils.scrollToView(span, win, offset);
      domUtils.remove(span);
      return me;
    },

    /**
         * 判断当前选区内容是否占位符
         * @private
         * @method inFillChar
         * @return { Boolean } 如果是占位符返回true，否则返回false
         */
    inFillChar: function() {
      var start = this.startContainer;
      if (
        this.collapsed &&
        start.nodeType == 3 &&
        start.nodeValue.replace(new RegExp("^" + domUtils.fillChar), "")
          .length +
          1 ==
          start.nodeValue.length
      ) {
        return true;
      }
      return false;
    },

    /**
         * 保存
         * @method createAddress
         * @private
         * @return { Boolean } 返回开始和结束的位置
         * @example
         * ```html
         * <body>
         *     <p>
         *         aaaa
         *         <em>
         *             <!-- 选区开始 -->
         *             bbbb
         *             <!-- 选区结束 -->
         *         </em>
         *     </p>
         *
         *     <script>
         *         //output: {startAddress:[0,1,0,0],endAddress:[0,1,0,4]}
         *         console.log( range.createAddress() );
         *     </script>
         * </body>
         * ```
         */
    createAddress: function(ignoreEnd, ignoreTxt) {
      var addr = {},
        me = this;

      function getAddress(isStart) {
        var node = isStart ? me.startContainer : me.endContainer;
        var parents = domUtils.findParents(node, true, function(node) {
          return !domUtils.isBody(node);
        }),
          addrs = [];
        for (var i = 0, ci; (ci = parents[i++]); ) {
          addrs.push(domUtils.getNodeIndex(ci, ignoreTxt));
        }
        var firstIndex = 0;

        if (ignoreTxt) {
          if (node.nodeType == 3) {
            var tmpNode = node.previousSibling;
            while (tmpNode && tmpNode.nodeType == 3) {
              firstIndex += tmpNode.nodeValue.replace(fillCharReg, "").length;
              tmpNode = tmpNode.previousSibling;
            }
            firstIndex += isStart ? me.startOffset : me.endOffset; // - (fillCharReg.test(node.nodeValue) ? 1 : 0 )
          } else {
            node = node.childNodes[isStart ? me.startOffset : me.endOffset];
            if (node) {
              firstIndex = domUtils.getNodeIndex(node, ignoreTxt);
            } else {
              node = isStart ? me.startContainer : me.endContainer;
              var first = node.firstChild;
              while (first) {
                if (domUtils.isFillChar(first)) {
                  first = first.nextSibling;
                  continue;
                }
                firstIndex++;
                if (first.nodeType == 3) {
                  while (first && first.nodeType == 3) {
                    first = first.nextSibling;
                  }
                } else {
                  first = first.nextSibling;
                }
              }
            }
          }
        } else {
          firstIndex = isStart
            ? domUtils.isFillChar(node) ? 0 : me.startOffset
            : me.endOffset;
        }
        if (firstIndex < 0) {
          firstIndex = 0;
        }
        addrs.push(firstIndex);
        return addrs;
      }
      addr.startAddress = getAddress(true);
      if (!ignoreEnd) {
        addr.endAddress = me.collapsed
          ? [].concat(addr.startAddress)
          : getAddress();
      }
      return addr;
    },

    /**
         * 保存
         * @method createAddress
         * @private
         * @return { Boolean } 返回开始和结束的位置
         * @example
         * ```html
         * <body>
         *     <p>
         *         aaaa
         *         <em>
         *             <!-- 选区开始 -->
         *             bbbb
         *             <!-- 选区结束 -->
         *         </em>
         *     </p>
         *
         *     <script>
         *         var range = editor.selection.getRange();
         *         range.moveToAddress({startAddress:[0,1,0,0],endAddress:[0,1,0,4]});
         *         range.select();
         *         //output: 'bbbb'
         *         console.log(editor.selection.getText());
         *     </script>
         * </body>
         * ```
         */
    moveToAddress: function(addr, ignoreEnd) {
      var me = this;
      function getNode(address, isStart) {
        var tmpNode = me.document.body,
          parentNode,
          offset;
        for (var i = 0, ci, l = address.length; i < l; i++) {
          ci = address[i];
          parentNode = tmpNode;
          tmpNode = tmpNode.childNodes[ci];
          if (!tmpNode) {
            offset = ci;
            break;
          }
        }
        if (isStart) {
          if (tmpNode) {
            me.setStartBefore(tmpNode);
          } else {
            me.setStart(parentNode, offset);
          }
        } else {
          if (tmpNode) {
            me.setEndBefore(tmpNode);
          } else {
            me.setEnd(parentNode, offset);
          }
        }
      }
      getNode(addr.startAddress, true);
      !ignoreEnd && addr.endAddress && getNode(addr.endAddress);
      return me;
    },

    /**
         * 判断给定的Range对象是否和当前Range对象表示的是同一个选区
         * @method equals
         * @param { UE.dom.Range } 需要判断的Range对象
         * @return { Boolean } 如果给定的Range对象与当前Range对象表示的是同一个选区， 则返回true， 否则返回false
         */
    equals: function(rng) {
      for (var p in this) {
        if (this.hasOwnProperty(p)) {
          if (this[p] !== rng[p]) return false;
        }
      }
      return true;
    },

    /**
         * 遍历range内的节点。每当遍历一个节点时， 都会执行参数项 doFn 指定的函数， 该函数的接受当前遍历的节点
         * 作为其参数。
         * @method traversal
         * @param { Function }  doFn 对每个遍历的节点要执行的方法， 该方法接受当前遍历的节点作为其参数
         * @return { UE.dom.Range } 当前range对象
         * @example
         * ```html
         *
         * <body>
         *
         *     <!-- 选区开始 -->
         *     <span></span>
         *     <a></a>
         *     <!-- 选区结束 -->
         * </body>
         *
         * <script>
         *
         *     //output: <span></span><a></a>
         *     console.log( range.cloneContents() );
         *
         *     range.traversal( function ( node ) {
         *
         *         if ( node.nodeType === 1 ) {
         *             node.className = "test";
         *         }
         *
         *     } );
         *
         *     //output: <span class="test"></span><a class="test"></a>
         *     console.log( range.cloneContents() );
         *
         * </script>
         * ```
         */

    /**
         * 遍历range内的节点。
         * 每当遍历一个节点时， 都会执行参数项 doFn 指定的函数， 该函数的接受当前遍历的节点
         * 作为其参数。
         * 可以通过参数项 filterFn 来指定一个过滤器， 只有符合该过滤器过滤规则的节点才会触
         * 发doFn函数的执行
         * @method traversal
         * @param { Function } doFn 对每个遍历的节点要执行的方法， 该方法接受当前遍历的节点作为其参数
         * @param { Function } filterFn 过滤器， 该函数接受当前遍历的节点作为参数， 如果该节点满足过滤
         *                      规则， 请返回true， 该节点会触发doFn， 否则， 请返回false， 则该节点不
         *                      会触发doFn。
         * @return { UE.dom.Range } 当前range对象
         * @see UE.dom.Range:traversal(Function)
         * @example
         * ```html
         *
         * <body>
         *
         *     <!-- 选区开始 -->
         *     <span></span>
         *     <a></a>
         *     <!-- 选区结束 -->
         * </body>
         *
         * <script>
         *
         *     //output: <span></span><a></a>
         *     console.log( range.cloneContents() );
         *
         *     range.traversal( function ( node ) {
         *
         *         node.className = "test";
         *
         *     }, function ( node ) {
         *          return node.nodeType === 1;
         *     } );
         *
         *     //output: <span class="test"></span><a class="test"></a>
         *     console.log( range.cloneContents() );
         *
         * </script>
         * ```
         */
    traversal: function(doFn, filterFn) {
      if (this.collapsed) return this;
      var bookmark = this.createBookmark(),
        end = bookmark.end,
        current = domUtils.getNextDomNode(bookmark.start, false, filterFn);
      while (
        current &&
        current !== end &&
        domUtils.getPosition(current, end) & domUtils.POSITION_PRECEDING
      ) {
        var tmpNode = domUtils.getNextDomNode(current, false, filterFn);
        doFn(current);
        current = tmpNode;
      }
      return this.moveToBookmark(bookmark);
    }
  };
})();


// core/Selection.js
/**
 * 选集
 * @file
 * @module UE.dom
 * @class Selection
 * @since 1.2.6.1
 */

/**
 * 选区集合
 * @unfile
 * @module UE.dom
 * @class Selection
 */
(function() {
  function getBoundaryInformation(range, start) {
    var getIndex = domUtils.getNodeIndex;
    range = range.duplicate();
    range.collapse(start);
    var parent = range.parentElement();
    //如果节点里没有子节点，直接退出
    if (!parent.hasChildNodes()) {
      return { container: parent, offset: 0 };
    }
    var siblings = parent.children,
      child,
      testRange = range.duplicate(),
      startIndex = 0,
      endIndex = siblings.length - 1,
      index = -1,
      distance;
    while (startIndex <= endIndex) {
      index = Math.floor((startIndex + endIndex) / 2);
      child = siblings[index];
      testRange.moveToElementText(child);
      var position = testRange.compareEndPoints("StartToStart", range);
      if (position > 0) {
        endIndex = index - 1;
      } else if (position < 0) {
        startIndex = index + 1;
      } else {
        //trace:1043
        return { container: parent, offset: getIndex(child) };
      }
    }
    if (index == -1) {
      testRange.moveToElementText(parent);
      testRange.setEndPoint("StartToStart", range);
      distance = testRange.text.replace(/(\r\n|\r)/g, "\n").length;
      siblings = parent.childNodes;
      if (!distance) {
        child = siblings[siblings.length - 1];
        return { container: child, offset: child.nodeValue.length };
      }

      var i = siblings.length;
      while (distance > 0) {
        distance -= siblings[--i].nodeValue.length;
      }
      return { container: siblings[i], offset: -distance };
    }
    testRange.collapse(position > 0);
    testRange.setEndPoint(position > 0 ? "StartToStart" : "EndToStart", range);
    distance = testRange.text.replace(/(\r\n|\r)/g, "\n").length;
    if (!distance) {
      return dtd.$empty[child.tagName] || dtd.$nonChild[child.tagName]
        ? {
            container: parent,
            offset: getIndex(child) + (position > 0 ? 0 : 1)
          }
        : {
            container: child,
            offset: position > 0 ? 0 : child.childNodes.length
          };
    }
    while (distance > 0) {
      try {
        var pre = child;
        child = child[position > 0 ? "previousSibling" : "nextSibling"];
        distance -= child.nodeValue.length;
      } catch (e) {
        return { container: parent, offset: getIndex(pre) };
      }
    }
    return {
      container: child,
      offset: position > 0 ? -distance : child.nodeValue.length + distance
    };
  }

  /**
     * 将ieRange转换为Range对象
     * @param {Range}   ieRange    ieRange对象
     * @param {Range}   range      Range对象
     * @return  {Range}  range       返回转换后的Range对象
     */
  function transformIERangeToRange(ieRange, range) {
    if (ieRange.item) {
      range.selectNode(ieRange.item(0));
    } else {
      var bi = getBoundaryInformation(ieRange, true);
      range.setStart(bi.container, bi.offset);
      if (ieRange.compareEndPoints("StartToEnd", ieRange) != 0) {
        bi = getBoundaryInformation(ieRange, false);
        range.setEnd(bi.container, bi.offset);
      }
    }
    return range;
  }

  /**
     * 获得ieRange
     * @param {Selection} sel    Selection对象
     * @return {ieRange}    得到ieRange
     */
  function _getIERange(sel) {
    var ieRange;
    //ie下有可能报错
    try {
      ieRange = sel.getNative().createRange();
    } catch (e) {
      return null;
    }
    var el = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
    if ((el.ownerDocument || el) === sel.document) {
      return ieRange;
    }
    return null;
  }

  var Selection = (dom.Selection = function(doc) {
    var me = this,
      iframe;
    me.document = doc;
    if (browser.ie9below) {
      iframe = domUtils.getWindow(doc).frameElement;
      domUtils.on(iframe, "beforedeactivate", function() {
        me._bakIERange = me.getIERange();
      });
      domUtils.on(iframe, "activate", function() {
        try {
          if (!_getIERange(me) && me._bakIERange) {
            me._bakIERange.select();
          }
        } catch (ex) {}
        me._bakIERange = null;
      });
    }
    iframe = doc = null;
  });

  Selection.prototype = {
    rangeInBody: function(rng, txtRange) {
      var node = browser.ie9below || txtRange
        ? rng.item ? rng.item() : rng.parentElement()
        : rng.startContainer;

      return node === this.document.body || domUtils.inDoc(node, this.document);
    },

    /**
         * 获取原生seleciton对象
         * @method getNative
         * @return { Object } 获得selection对象
         * @example
         * ```javascript
         * editor.selection.getNative();
         * ```
         */
    getNative: function() {
      var doc = this.document;
      try {
        return !doc
          ? null
          : browser.ie9below
            ? doc.selection
            : domUtils.getWindow(doc).getSelection();
      } catch (e) {
        return null;
      }
    },

    /**
         * 获得ieRange
         * @method getIERange
         * @return { Object } 返回ie原生的Range
         * @example
         * ```javascript
         * editor.selection.getIERange();
         * ```
         */
    getIERange: function() {
      var ieRange = _getIERange(this);
      if (!ieRange) {
        if (this._bakIERange) {
          return this._bakIERange;
        }
      }
      return ieRange;
    },

    /**
         * 缓存当前选区的range和选区的开始节点
         * @method cache
         */
    cache: function() {
      this.clear();
      this._cachedRange = this.getRange();
      this._cachedStartElement = this.getStart();
      this._cachedStartElementPath = this.getStartElementPath();
    },

    /**
         * 获取选区开始位置的父节点到body
         * @method getStartElementPath
         * @return { Array } 返回父节点集合
         * @example
         * ```javascript
         * editor.selection.getStartElementPath();
         * ```
         */
    getStartElementPath: function() {
      if (this._cachedStartElementPath) {
        return this._cachedStartElementPath;
      }
      var start = this.getStart();
      if (start) {
        return domUtils.findParents(start, true, null, true);
      }
      return [];
    },

    /**
         * 清空缓存
         * @method clear
         */
    clear: function() {
      this._cachedStartElementPath = this._cachedRange = this._cachedStartElement = null;
    },

    /**
         * 编辑器是否得到了选区
         * @method isFocus
         */
    isFocus: function() {
      try {
        if (browser.ie9below) {
          var nativeRange = _getIERange(this);
          return !!(nativeRange && this.rangeInBody(nativeRange));
        } else {
          return !!this.getNative().rangeCount;
        }
      } catch (e) {
        return false;
      }
    },

    /**
         * 获取选区对应的Range
         * @method getRange
         * @return { Object } 得到Range对象
         * @example
         * ```javascript
         * editor.selection.getRange();
         * ```
         */
    getRange: function() {
      var me = this;
      function optimze(range) {
        var child = me.document.body.firstChild,
          collapsed = range.collapsed;
        while (child && child.firstChild) {
          range.setStart(child, 0);
          child = child.firstChild;
        }
        if (!range.startContainer) {
          range.setStart(me.document.body, 0);
        }
        if (collapsed) {
          range.collapse(true);
        }
      }

      if (me._cachedRange != null) {
        return this._cachedRange;
      }
      var range = new baidu.editor.dom.Range(me.document);

      if (browser.ie9below) {
        var nativeRange = me.getIERange();
        if (nativeRange) {
          //备份的_bakIERange可能已经实效了，dom树发生了变化比如从源码模式切回来，所以try一下，实效就放到body开始位置
          try {
            transformIERangeToRange(nativeRange, range);
          } catch (e) {
            optimze(range);
          }
        } else {
          optimze(range);
        }
      } else {
        var sel = me.getNative();
        if (sel && sel.rangeCount) {
          var firstRange = sel.getRangeAt(0);
          var lastRange = sel.getRangeAt(sel.rangeCount - 1);
          range
            .setStart(firstRange.startContainer, firstRange.startOffset)
            .setEnd(lastRange.endContainer, lastRange.endOffset);
          if (
            range.collapsed &&
            domUtils.isBody(range.startContainer) &&
            !range.startOffset
          ) {
            optimze(range);
          }
        } else {
          //trace:1734 有可能已经不在dom树上了，标识的节点
          if (
            this._bakRange &&
            domUtils.inDoc(this._bakRange.startContainer, this.document)
          ) {
            return this._bakRange;
          }
          optimze(range);
        }
      }
      return (this._bakRange = range);
    },

    /**
         * 获取开始元素，用于状态反射
         * @method getStart
         * @return { Element } 获得开始元素
         * @example
         * ```javascript
         * editor.selection.getStart();
         * ```
         */
    getStart: function() {
      if (this._cachedStartElement) {
        return this._cachedStartElement;
      }
      var range = browser.ie9below ? this.getIERange() : this.getRange(),
        tmpRange,
        start,
        tmp,
        parent;
      if (browser.ie9below) {
        if (!range) {
          //todo 给第一个值可能会有问题
          return this.document.body.firstChild;
        }
        //control元素
        if (range.item) {
          return range.item(0);
        }
        tmpRange = range.duplicate();
        //修正ie下<b>x</b>[xx] 闭合后 <b>x|</b>xx
        tmpRange.text.length > 0 && tmpRange.moveStart("character", 1);
        tmpRange.collapse(1);
        start = tmpRange.parentElement();
        parent = tmp = range.parentElement();
        while ((tmp = tmp.parentNode)) {
          if (tmp == start) {
            start = parent;
            break;
          }
        }
      } else {
        range.shrinkBoundary();
        start = range.startContainer;
        if (start.nodeType == 1 && start.hasChildNodes()) {
          start =
            start.childNodes[
              Math.min(start.childNodes.length - 1, range.startOffset)
            ];
        }
        if (start.nodeType == 3) {
          return start.parentNode;
        }
      }
      return start;
    },

    /**
         * 得到选区中的文本
         * @method getText
         * @return { String } 选区中包含的文本
         * @example
         * ```javascript
         * editor.selection.getText();
         * ```
         */
    getText: function() {
      var nativeSel, nativeRange;
      if (this.isFocus() && (nativeSel = this.getNative())) {
        nativeRange = browser.ie9below
          ? nativeSel.createRange()
          : nativeSel.getRangeAt(0);
        return browser.ie9below ? nativeRange.text : nativeRange.toString();
      }
      return "";
    },

    /**
         * 清除选区
         * @method clearRange
         * @example
         * ```javascript
         * editor.selection.clearRange();
         * ```
         */
    clearRange: function() {
      this.getNative()[browser.ie9below ? "empty" : "removeAllRanges"]();
    }
  };
})();


// core/Editor.js
/**
 * 编辑器主类，包含编辑器提供的大部分公用接口
 * @file
 * @module UE
 * @class Editor
 * @since 1.2.6.1
 */

/**
 * UEditor公用空间，UEditor所有的功能都挂载在该空间下
 * @unfile
 * @module UE
 */

/**
 * UEditor的核心类，为用户提供与编辑器交互的接口。
 * @unfile
 * @module UE
 * @class Editor
 */

(function() {
  var uid = 0,
    _selectionChangeTimer;

  /**
     * 获取编辑器的html内容，赋值到编辑器所在表单的textarea文本域里面
     * @private
     * @method setValue
     * @param { UE.Editor } editor 编辑器事例
     */
  function setValue(form, editor) {
    var textarea;
    if (editor.options.textarea) {
      if (utils.isString(editor.options.textarea)) {
        for (
          var i = 0, ti, tis = domUtils.getElementsByTagName(form, "textarea");
          (ti = tis[i++]);

        ) {
          if (ti.id == "ueditor_textarea_" + editor.options.textarea) {
            textarea = ti;
            break;
          }
        }
      } else {
        textarea = editor.textarea;
      }
    }
    if (!textarea) {
      form.appendChild(
        (textarea = domUtils.createElement(document, "textarea", {
          name: editor.options.textarea,
          id: "ueditor_textarea_" + editor.options.textarea,
          style: "display:none"
        }))
      );
      //不要产生多个textarea
      editor.textarea = textarea;
    }
    !textarea.getAttribute("name") &&
      textarea.setAttribute("name", editor.options.textarea);
    textarea.value = editor.hasContents()
      ? editor.options.allHtmlEnabled
        ? editor.getAllHtml()
        : editor.getContent(null, null, true)
      : "";
  }
  function loadPlugins(me) {
    //初始化插件
    for (var pi in UE.plugins) {
      UE.plugins[pi].call(me);
    }
  }
  function checkCurLang(I18N) {
    for (var lang in I18N) {
      return lang;
    }
  }

  function langReadied(me) {
    me.langIsReady = true;

    me.fireEvent("langReady");
  }

  /**
     * 编辑器准备就绪后会触发该事件
     * @module UE
     * @class Editor
     * @event ready
     * @remind render方法执行完成之后,会触发该事件
     * @remind
     * @example
     * ```javascript
     * editor.addListener( 'ready', function( editor ) {
     *     editor.execCommand( 'focus' ); //编辑器家在完成后，让编辑器拿到焦点
     * } );
     * ```
     */
  /**
     * 执行destroy方法,会触发该事件
     * @module UE
     * @class Editor
     * @event destroy
     * @see UE.Editor:destroy()
     */
  /**
     * 执行reset方法,会触发该事件
     * @module UE
     * @class Editor
     * @event reset
     * @see UE.Editor:reset()
     */
  /**
     * 执行focus方法,会触发该事件
     * @module UE
     * @class Editor
     * @event focus
     * @see UE.Editor:focus(Boolean)
     */
  /**
     * 语言加载完成会触发该事件
     * @module UE
     * @class Editor
     * @event langReady
     */
  /**
     * 运行命令之后会触发该命令
     * @module UE
     * @class Editor
     * @event beforeExecCommand
     */
  /**
     * 运行命令之后会触发该命令
     * @module UE
     * @class Editor
     * @event afterExecCommand
     */
  /**
     * 运行命令之前会触发该命令
     * @module UE
     * @class Editor
     * @event firstBeforeExecCommand
     */
  /**
     * 在getContent方法执行之前会触发该事件
     * @module UE
     * @class Editor
     * @event beforeGetContent
     * @see UE.Editor:getContent()
     */
  /**
     * 在getContent方法执行之后会触发该事件
     * @module UE
     * @class Editor
     * @event afterGetContent
     * @see UE.Editor:getContent()
     */
  /**
     * 在getAllHtml方法执行时会触发该事件
     * @module UE
     * @class Editor
     * @event getAllHtml
     * @see UE.Editor:getAllHtml()
     */
  /**
     * 在setContent方法执行之前会触发该事件
     * @module UE
     * @class Editor
     * @event beforeSetContent
     * @see UE.Editor:setContent(String)
     */
  /**
     * 在setContent方法执行之后会触发该事件
     * @module UE
     * @class Editor
     * @event afterSetContent
     * @see UE.Editor:setContent(String)
     */
  /**
     * 每当编辑器内部选区发生改变时，将触发该事件
     * @event selectionchange
     * @warning 该事件的触发非常频繁，不建议在该事件的处理过程中做重量级的处理
     * @example
     * ```javascript
     * editor.addListener( 'selectionchange', function( editor ) {
     *     console.log('选区发生改变');
     * }
     */
  /**
     * 在所有selectionchange的监听函数执行之前，会触发该事件
     * @module UE
     * @class Editor
     * @event beforeSelectionChange
     * @see UE.Editor:selectionchange
     */
  /**
     * 在所有selectionchange的监听函数执行完之后，会触发该事件
     * @module UE
     * @class Editor
     * @event afterSelectionChange
     * @see UE.Editor:selectionchange
     */
  /**
     * 编辑器内容发生改变时会触发该事件
     * @module UE
     * @class Editor
     * @event contentChange
     */

  /**
     * 以默认参数构建一个编辑器实例
     * @constructor
     * @remind 通过 改构造方法实例化的编辑器,不带ui层.需要render到一个容器,编辑器实例才能正常渲染到页面
     * @example
     * ```javascript
     * var editor = new UE.Editor();
     * editor.execCommand('blod');
     * ```
     * @see UE.Config
     */

  /**
     * 以给定的参数集合创建一个编辑器实例，对于未指定的参数，将应用默认参数。
     * @constructor
     * @remind 通过 改构造方法实例化的编辑器,不带ui层.需要render到一个容器,编辑器实例才能正常渲染到页面
     * @param { Object } setting 创建编辑器的参数
     * @example
     * ```javascript
     * var editor = new UE.Editor();
     * editor.execCommand('blod');
     * ```
     * @see UE.Config
     */
  var Editor = (UE.Editor = function(options) {
    var me = this;
    me.uid = uid++;
    EventBase.call(me);
    me.commands = {};
    me.options = utils.extend(utils.clone(options || {}), UEDITOR_CONFIG, true);
    me.shortcutkeys = {};
    me.inputRules = [];
    me.outputRules = [];
    //设置默认的常用属性
    me.setOpt(Editor.defaultOptions(me));

    /* 尝试异步加载后台配置 */
    me.loadServerConfig();

    if (!utils.isEmptyObject(UE.I18N)) {
      //修改默认的语言类型
      me.options.lang = checkCurLang(UE.I18N);
      UE.plugin.load(me);
      langReadied(me);
    } else {
      utils.loadFile(
        document,
        {
          src:
            me.options.langPath +
              me.options.lang +
              "/" +
              me.options.lang +
              ".js?20230319",
          tag: "script",
          type: "text/javascript",
          defer: "defer"
        },
        function() {
          UE.plugin.load(me);
          langReadied(me);
        }
      );
    }

    UE.instants["ueditorInstant" + me.uid] = me;
  });
  Editor.prototype = {
    registerCommand: function(name, obj) {
      this.commands[name] = obj;
    },
    /**
         * 编辑器对外提供的监听ready事件的接口， 通过调用该方法，达到的效果与监听ready事件是一致的
         * @method ready
         * @param { Function } fn 编辑器ready之后所执行的回调, 如果在注册事件之前编辑器已经ready，将会
         * 立即触发该回调。
         * @remind 需要等待编辑器加载完成后才能执行的代码,可以使用该方法传入
         * @example
         * ```javascript
         * editor.ready( function( editor ) {
         *     editor.setContent('初始化完毕');
         * } );
         * ```
         * @see UE.Editor.event:ready
         */
    ready: function(fn) {
      var me = this;
      if (fn) {
        me.isReady ? fn.apply(me) : me.addListener("ready", fn);
      }
    },

    /**
         * 该方法是提供给插件里面使用，设置配置项默认值
         * @method setOpt
         * @warning 三处设置配置项的优先级: 实例化时传入参数 > setOpt()设置 > config文件里设置
         * @warning 该方法仅供编辑器插件内部和编辑器初始化时调用，其他地方不能调用。
         * @param { String } key 编辑器的可接受的选项名称
         * @param { * } val  该选项可接受的值
         * @example
         * ```javascript
         * editor.setOpt( 'initContent', '欢迎使用编辑器' );
         * ```
         */

    /**
         * 该方法是提供给插件里面使用，以{key:value}集合的方式设置插件内用到的配置项默认值
         * @method setOpt
         * @warning 三处设置配置项的优先级: 实例化时传入参数 > setOpt()设置 > config文件里设置
         * @warning 该方法仅供编辑器插件内部和编辑器初始化时调用，其他地方不能调用。
         * @param { Object } options 将要设置的选项的键值对对象
         * @example
         * ```javascript
         * editor.setOpt( {
         *     'initContent': '欢迎使用编辑器'
         * } );
         * ```
         */
    setOpt: function(key, val) {
      var obj = {};
      if (utils.isString(key)) {
        obj[key] = val;
      } else {
        obj = key;
      }
      utils.extend(this.options, obj, true);
    },
    getOpt: function(key) {
      return this.options[key];
    },
    /**
         * 销毁编辑器实例，使用textarea代替
         * @method destroy
         * @example
         * ```javascript
         * editor.destroy();
         * ```
         */
    destroy: function() {
      var me = this;
      me.fireEvent("destroy");
      var container = me.container.parentNode;
      var textarea = me.textarea;
      if (!textarea) {
        textarea = document.createElement("textarea");
        container.parentNode.insertBefore(textarea, container);
      } else {
        textarea.style.display = "";
      }

      textarea.style.width = me.iframe.offsetWidth + "px";
      textarea.style.height = me.iframe.offsetHeight + "px";
      textarea.value = me.getContent();
      textarea.id = me.key;
      container.innerHTML = "";
      domUtils.remove(container);
      var key = me.key;
      //trace:2004
      for (var p in me) {
        if (me.hasOwnProperty(p)) {
          delete this[p];
        }
      }
      UE.delEditor(key);
    },

    /**
         * 渲染编辑器的DOM到指定容器
         * @method render
         * @param { String } containerId 指定一个容器ID
         * @remind 执行该方法,会触发ready事件
         * @warning 必须且只能调用一次
         */

    /**
         * 渲染编辑器的DOM到指定容器
         * @method render
         * @param { Element } containerDom 直接指定容器对象
         * @remind 执行该方法,会触发ready事件
         * @warning 必须且只能调用一次
         */
    render: function(container) {
      var me = this,
        options = me.options,
        getStyleValue = function(attr) {
          return parseInt(domUtils.getComputedStyle(container, attr));
        };
      if (utils.isString(container)) {
        container = document.getElementById(container);
      }
      if (container) {
        if (options.initialFrameWidth) {
          options.minFrameWidth = options.initialFrameWidth;
        } else {
          options.minFrameWidth = options.initialFrameWidth =
            container.offsetWidth;
        }
        if (options.initialFrameHeight) {
          options.minFrameHeight = options.initialFrameHeight;
        } else {
          options.initialFrameHeight = options.minFrameHeight =
            container.offsetHeight;
        }

        container.style.width = /%$/.test(options.initialFrameWidth)
          ? "100%"
          : options.initialFrameWidth -
              getStyleValue("padding-left") -
              getStyleValue("padding-right") +
              "px";
        container.style.height = /%$/.test(options.initialFrameHeight)
          ? "100%"
          : options.initialFrameHeight -
              getStyleValue("padding-top") -
              getStyleValue("padding-bottom") +
              "px";

        container.style.zIndex = options.zIndex;
        var additionCssHtml = [];
        for(var i in options.iframeCssUrlsAddition){
            additionCssHtml.push("<link rel='stylesheet' type='text/css' href='" + utils.unhtml(options.iframeCssUrlsAddition[i]) + "'/>")
        }
        var html =
          (ie && browser.version < 9 ? "" : "<!DOCTYPE html>") +
          "<html xmlns='http://www.w3.org/1999/xhtml' class='view' >" +
          "<head>" +
          "<style type='text/css'>" +
          //设置四周的留边
          ".view{padding:0;word-wrap:break-word;cursor:text;height:90%;}\n" +
          //设置默认字体和字号
          //font-family不能呢随便改，在safari下fillchar会有解析问题
          "body{margin:8px;font-family:sans-serif;font-size:16px;}" +
          //设置段落间距
          "p{margin:5px 0;}</style>" +
          (options.iframeCssUrl
            ? "<link rel='stylesheet' type='text/css' href='" +
                utils.unhtml(options.iframeCssUrl) +
                "'/>"
            : "") +
          (options.initialStyle
            ? "<style>" + options.initialStyle + "</style>"
            : "") +
          additionCssHtml.join("") +
          "</head>" +
          "<body class='view' ></body>" +
          "<script type='text/javascript' " +
          (ie ? "defer='defer'" : "") +
          " id='_initialScript'>" +
          "setTimeout(function(){editor = window.parent.UE.instants['ueditorInstant" +
          me.uid +
          "'];editor._setup(document);},0);" +
          "var _tmpScript = document.getElementById('_initialScript');_tmpScript.parentNode.removeChild(_tmpScript);" +
          "</script>" +
          (options.iframeJsUrl
            ? "<script type='text/javascript' src='" +
                utils.unhtml(options.iframeJsUrl) +
                "'></script>"
            : "") +
          "</html>";

        container.appendChild(
          domUtils.createElement(document, "iframe", {
            id: "ueditor_" + me.uid,
            width: "100%",
            height: "100%",
            frameborder: "0",
            //先注释掉了，加的原因忘记了，但开启会直接导致全屏模式下内容多时不会出现滚动条
            //                    scrolling :'no',
            src:
              "javascript:void(function(){document.open();" +
                (options.customDomain && document.domain != location.hostname
                  ? 'document.domain="' + document.domain + '";'
                  : "") +
                'document.write("' +
                html +
                '");document.close();}())'
          })
        );
        container.style.overflow = "hidden";
        //解决如果是给定的百分比，会导致高度算不对的问题
        setTimeout(function() {
          if (/%$/.test(options.initialFrameWidth)) {
            options.minFrameWidth = options.initialFrameWidth =
              container.offsetWidth;
            //如果这里给定宽度，会导致ie在拖动窗口大小时，编辑区域不随着变化
            //                        container.style.width = options.initialFrameWidth + 'px';
          }
          if (/%$/.test(options.initialFrameHeight)) {
            options.minFrameHeight = options.initialFrameHeight =
              container.offsetHeight;
            container.style.height = options.initialFrameHeight + "px";
          }
        });
      }
    },

    /**
         * 编辑器初始化
         * @method _setup
         * @private
         * @param { Element } doc 编辑器Iframe中的文档对象
         */
    _setup: function(doc) {
      var me = this,
        options = me.options;
      if (ie) {
        doc.body.disabled = true;
        doc.body.contentEditable = true;
        doc.body.disabled = false;
      } else {
        doc.body.contentEditable = true;
      }
      doc.body.spellcheck = false;
      me.document = doc;
      me.window = doc.defaultView || doc.parentWindow;
      me.iframe = me.window.frameElement;
      me.body = doc.body;
      me.selection = new dom.Selection(doc);
      //gecko初始化就能得到range,无法判断isFocus了
      var geckoSel;
      if (browser.gecko && (geckoSel = this.selection.getNative())) {
        geckoSel.removeAllRanges();
      }
      this._initEvents();
      //为form提交提供一个隐藏的textarea
      for (
        var form = this.iframe.parentNode;
        !domUtils.isBody(form);
        form = form.parentNode
      ) {
        if (form.tagName === "FORM") {
          me.form = form;
          if (me.options.autoSyncData) {
            domUtils.on(me.window, "blur", function() {
              setValue(form, me);
            });
            domUtils.on(form, "submit", function() {
              me.fireEvent("beforesubmit");
            });
          } else {
            domUtils.on(form, "submit", function() {
              setValue(this, me);
              me.fireEvent("beforesubmit");
            });
          }
          break;
        }
      }
      if (options.initialContent) {
        if (options.autoClearinitialContent) {
          var oldExecCommand = me.execCommand;
          me.execCommand = function() {
            me.fireEvent("firstBeforeExecCommand");
            return oldExecCommand.apply(me, arguments);
          };
          this._setDefaultContent(options.initialContent);
        } else this.setContent(options.initialContent, false, true);
      }

      //编辑器不能为空内容

      if (domUtils.isEmptyNode(me.body)) {
        me.body.innerHTML = "<p>" + (browser.ie ? "" : "<br/>") + "</p>";
      }
      //如果要求focus, 就把光标定位到内容开始
      if (options.focus) {
        setTimeout(function() {
          me.focus(me.options.focusInEnd);
          //如果自动清除开着，就不需要做selectionchange;
          !me.options.autoClearinitialContent && me._selectionChange();
        }, 0);
      }
      if (!me.container) {
        me.container = this.iframe.parentNode;
      }
      if (options.fullscreen && me.ui) {
        me.ui.setFullScreen(true);
      }

      try {
        me.document.execCommand("2D-position", false, false);
      } catch (e) {}
      try {
        me.document.execCommand("enableInlineTableEditing", false, false);
      } catch (e) {}
      try {
        me.document.execCommand("enableObjectResizing", false, false);
      } catch (e) {}

      //挂接快捷键
      me._bindshortcutKeys();
      me.isReady = 1;
      me.fireEvent("ready");
      options.onready && options.onready.call(me);
      if (!browser.ie9below) {
        domUtils.on(me.window, ["blur", "focus"], function(e) {
          //chrome下会出现alt+tab切换时，导致选区位置不对
          if (e.type == "blur") {
            me._bakRange = me.selection.getRange();
            try {
              me._bakNativeRange = me.selection.getNative().getRangeAt(0);
              me.selection.getNative().removeAllRanges();
            } catch (e) {
              me._bakNativeRange = null;
            }
          } else {
            try {
              me._bakRange && me._bakRange.select();
            } catch (e) {}
          }
        });
      }
      //trace:1518 ff3.6body不够寛，会导致点击空白处无法获得焦点
      if (browser.gecko && browser.version <= 10902) {
        //修复ff3.6初始化进来，不能点击获得焦点
        me.body.contentEditable = false;
        setTimeout(function() {
          me.body.contentEditable = true;
        }, 100);
        setInterval(function() {
          me.body.style.height = me.iframe.offsetHeight - 20 + "px";
        }, 100);
      }

      !options.isShow && me.setHide();
      options.readonly && me.setDisabled();
    },

    /**
         * 同步数据到编辑器所在的form
         * 从编辑器的容器节点向上查找form元素，若找到，就同步编辑内容到找到的form里，为提交数据做准备，主要用于是手动提交的情况
         * 后台取得数据的键值，使用你容器上的name属性，如果没有就使用参数里的textarea项
         * @method sync
         * @example
         * ```javascript
         * editor.sync();
         * form.sumbit(); //form变量已经指向了form元素
         * ```
         */

    /**
         * 根据传入的formId，在页面上查找要同步数据的表单，若找到，就同步编辑内容到找到的form里，为提交数据做准备
         * 后台取得数据的键值，该键值默认使用给定的编辑器容器的name属性，如果没有name属性则使用参数项里给定的“textarea”项
         * @method sync
         * @param { String } formID 指定一个要同步数据的form的id,编辑器的数据会同步到你指定form下
         */
    sync: function(formId) {
      var me = this,
        form = formId
          ? document.getElementById(formId)
          : domUtils.findParent(
              me.iframe.parentNode,
              function(node) {
                return node.tagName === "FORM";
              },
              true
            );
      form && setValue(form, me);
    },

    /**
     * 手动触发更新按钮栏状态
     */
    syncCommandState: function(){
      this.fireEvent("selectionchange");
    },

    /**
     * 设置编辑器宽度
     * @param width
     */
    setWidth: function(width){
      if (width !== parseInt(this.iframe.parentNode.parentNode.style.width)) {
        this.iframe.parentNode.parentNode.style.width = width + "px";
      }
    },

    /**
         * 设置编辑器高度
         * @method setHeight
         * @remind 当配置项autoHeightEnabled为真时,该方法无效
         * @param { Number } number 设置的高度值，纯数值，不带单位
         * @example
         * ```javascript
         * editor.setHeight(number);
         * ```
         */
    setHeight: function(height, notSetHeight) {
      if (height !== parseInt(this.iframe.parentNode.style.height)) {
        this.iframe.parentNode.style.height = height + "px";
      }
      !notSetHeight &&
        (this.options.minFrameHeight = this.options.initialFrameHeight = height);
      this.body.style.height = height + "px";
      !notSetHeight && this.trigger("setHeight");
    },

    /**
         * 为编辑器的编辑命令提供快捷键
         * 这个接口是为插件扩展提供的接口,主要是为新添加的插件，如果需要添加快捷键，所提供的接口
         * @method addshortcutkey
         * @param { Object } keyset 命令名和快捷键键值对对象，多个按钮的快捷键用“＋”分隔
         * @example
         * ```javascript
         * editor.addshortcutkey({
         *     "Bold" : "ctrl+66",//^B
         *     "Italic" : "ctrl+73", //^I
         * });
         * ```
         */
    /**
         * 这个接口是为插件扩展提供的接口,主要是为新添加的插件，如果需要添加快捷键，所提供的接口
         * @method addshortcutkey
         * @param { String } cmd 触发快捷键时，响应的命令
         * @param { String } keys 快捷键的字符串，多个按钮用“＋”分隔
         * @example
         * ```javascript
         * editor.addshortcutkey("Underline", "ctrl+85"); //^U
         * ```
         */
    addshortcutkey: function(cmd, keys) {
      var obj = {};
      if (keys) {
        obj[cmd] = keys;
      } else {
        obj = cmd;
      }
      utils.extend(this.shortcutkeys, obj);
    },

    /**
         * 对编辑器设置keydown事件监听，绑定快捷键和命令，当快捷键组合触发成功，会响应对应的命令
         * @method _bindshortcutKeys
         * @private
         */
    _bindshortcutKeys: function() {
      var me = this,
        shortcutkeys = this.shortcutkeys;
      me.addListener("keydown", function(type, e) {
        var keyCode = e.keyCode || e.which;
        for (var i in shortcutkeys) {
          var tmp = shortcutkeys[i].split(",");
          for (var t = 0, ti; (ti = tmp[t++]); ) {
            ti = ti.split(":");
            var key = ti[0],
              param = ti[1];
            if (
              /^(ctrl)(\+shift)?\+(\d+)$/.test(key.toLowerCase()) ||
              /^(\d+)$/.test(key)
            ) {
              if (
                ((RegExp.$1 == "ctrl" ? e.ctrlKey || e.metaKey : 0) &&
                  (RegExp.$2 != "" ? e[RegExp.$2.slice(1) + "Key"] : 1) &&
                  keyCode == RegExp.$3) ||
                keyCode == RegExp.$1
              ) {
                if (me.queryCommandState(i, param) != -1)
                  me.execCommand(i, param);
                domUtils.preventDefault(e);
              }
            }
          }
        }
      });
    },

    /**
         * 获取编辑器的内容
         * @method getContent
         * @warning 该方法获取到的是经过编辑器内置的过滤规则进行过滤后得到的内容
         * @return { String } 编辑器的内容字符串, 如果编辑器的内容为空，或者是空的标签内容（如:”&lt;p&gt;&lt;br/&gt;&lt;/p&gt;“）， 则返回空字符串
         * @example
         * ```javascript
         * //编辑器html内容:<p>1<strong>2<em>34</em>5</strong>6</p>
         * var content = editor.getContent(); //返回值:<p>1<strong>2<em>34</em>5</strong>6</p>
         * ```
         */

    /**
         * 获取编辑器的内容。 可以通过参数定义编辑器内置的判空规则
         * @method getContent
         * @param { Function } fn 自定的判空规则， 要求该方法返回一个boolean类型的值，
         *                      代表当前编辑器的内容是否空，
         *                      如果返回true， 则该方法将直接返回空字符串；如果返回false，则编辑器将返回
         *                      经过内置过滤规则处理后的内容。
         * @remind 该方法在处理包含有初始化内容的时候能起到很好的作用。
         * @warning 该方法获取到的是经过编辑器内置的过滤规则进行过滤后得到的内容
         * @return { String } 编辑器的内容字符串
         * @example
         * ```javascript
         * // editor 是一个编辑器的实例
         * var content = editor.getContent( function ( editor ) {
         *      return editor.body.innerHTML === '欢迎使用UEditor'; //返回空字符串
         * } );
         * ```
         */
    getContent: function(cmd, fn, notSetCursor, ignoreBlank, formatter) {
      var me = this;
      if (cmd && utils.isFunction(cmd)) {
        fn = cmd;
        cmd = "";
      }
      if (fn ? !fn() : !this.hasContents()) {
        return "";
      }
      me.fireEvent("beforegetcontent");
      var root = UE.htmlparser(me.body.innerHTML, ignoreBlank);
      me.filterOutputRule(root);
      me.fireEvent("aftergetcontent", cmd, root);
      return root.toHtml(formatter);
    },

    /**
         * 取得完整的html代码，可以直接显示成完整的html文档
         * @method getAllHtml
         * @return { String } 编辑器的内容html文档字符串
         * @eaxmple
         * ```javascript
         * editor.getAllHtml(); //返回格式大致是: <html><head>...</head><body>...</body></html>
         * ```
         */
    getAllHtml: function() {
      var me = this,
        headHtml = [],
        html = "";
      me.fireEvent("getAllHtml", headHtml);
      if (browser.ie && browser.version > 8) {
        var headHtmlForIE9 = "";
        utils.each(me.document.styleSheets, function(si) {
          headHtmlForIE9 += si.href
            ? '<link rel="stylesheet" type="text/css" href="' + si.href + '" />'
            : "<style>" + si.cssText + "</style>";
        });
        utils.each(me.document.getElementsByTagName("script"), function(si) {
          headHtmlForIE9 += si.outerHTML;
        });
      }
      return (
        "<html><head>" +
        (me.options.charset
          ? '<meta http-equiv="Content-Type" content="text/html; charset=' +
              me.options.charset +
              '"/>'
          : "") +
        (headHtmlForIE9 ||
          me.document.getElementsByTagName("head")[0].innerHTML) +
        headHtml.join("\n") +
        "</head>" +
        "<body " +
        (ie && browser.version < 9 ? 'class="view"' : "") +
        ">" +
        me.getContent(null, null, true) +
        "</body></html>"
      );
    },

    /**
         * 得到编辑器的纯文本内容，但会保留段落格式
         * @method getPlainTxt
         * @return { String } 编辑器带段落格式的纯文本内容字符串
         * @example
         * ```javascript
         * //编辑器html内容:<p><strong>1</strong></p><p><strong>2</strong></p>
         * console.log(editor.getPlainTxt()); //输出:"1\n2\n
         * ```
         */
    getPlainTxt: function() {
      var reg = new RegExp(domUtils.fillChar, "g"),
        html = this.body.innerHTML.replace(/[\n\r]/g, ""); //ie要先去了\n在处理
      html = html
        .replace(/<(p|div)[^>]*>(<br\/?>|&nbsp;)<\/\1>/gi, "\n")
        .replace(/<br\/?>/gi, "\n")
        .replace(/<[^>/]+>/g, "")
        .replace(/(\n)?<\/([^>]+)>/g, function(a, b, c) {
          return dtd.$block[c] ? "\n" : b ? b : "";
        });
      //取出来的空格会有c2a0会变成乱码，处理这种情况\u00a0
      return html
        .replace(reg, "")
        .replace(/\u00a0/g, " ")
        .replace(/&nbsp;/g, " ");
    },

    /**
         * 获取编辑器中的纯文本内容,没有段落格式
         * @method getContentTxt
         * @return { String } 编辑器不带段落格式的纯文本内容字符串
         * @example
         * ```javascript
         * //编辑器html内容:<p><strong>1</strong></p><p><strong>2</strong></p>
         * console.log(editor.getPlainTxt()); //输出:"12
         * ```
         */
    getContentTxt: function() {
      var reg = new RegExp(domUtils.fillChar, "g");
      //取出来的空格会有c2a0会变成乱码，处理这种情况\u00a0
      return this.body[browser.ie ? "innerText" : "textContent"]
        .replace(reg, "")
        .replace(/\u00a0/g, " ");
    },

    /**
         * 设置编辑器的内容，可修改编辑器当前的html内容
         * @method setContent
         * @warning 通过该方法插入的内容，是经过编辑器内置的过滤规则进行过滤后得到的内容
         * @warning 该方法会触发selectionchange事件
         * @param { String } html 要插入的html内容
         * @example
         * ```javascript
         * editor.getContent('<p>test</p>');
         * ```
         */

    /**
         * 设置编辑器的内容，可修改编辑器当前的html内容
         * @method setContent
         * @warning 通过该方法插入的内容，是经过编辑器内置的过滤规则进行过滤后得到的内容
         * @warning 该方法会触发selectionchange事件
         * @param { String } html 要插入的html内容
         * @param { Boolean } isAppendTo 若传入true，不清空原来的内容，在最后插入内容，否则，清空内容再插入
         * @example
         * ```javascript
         * //假设设置前的编辑器内容是 <p>old text</p>
         * editor.setContent('<p>new text</p>', true); //插入的结果是<p>old text</p><p>new text</p>
         * ```
         */
    setContent: function(html, isAppendTo, notFireSelectionchange) {
      var me = this;

      me.fireEvent("beforesetcontent", html);
      var root = UE.htmlparser(html);
      me.filterInputRule(root);
      html = root.toHtml();

      me.body.innerHTML = (isAppendTo ? me.body.innerHTML : "") + html;

      function isCdataDiv(node) {
        return node.tagName == "DIV" && node.getAttribute("cdata_tag");
      }
      //给文本或者inline节点套p标签
      if (me.options.enterTag == "p") {
        var child = this.body.firstChild,
          tmpNode;
        if (
          !child ||
          (child.nodeType == 1 &&
            (dtd.$cdata[child.tagName] ||
              isCdataDiv(child) ||
              domUtils.isCustomeNode(child)) &&
            child === this.body.lastChild)
        ) {
          this.body.innerHTML =
            "<p>" +
            (browser.ie ? "&nbsp;" : "<br/>") +
            "</p>" +
            this.body.innerHTML;
        } else {
          var p = me.document.createElement("p");
          while (child) {
            while (
              child &&
              (child.nodeType == 3 ||
                (child.nodeType == 1 &&
                  dtd.p[child.tagName] &&
                  !dtd.$cdata[child.tagName]))
            ) {
              tmpNode = child.nextSibling;
              p.appendChild(child);
              child = tmpNode;
            }
            if (p.firstChild) {
              if (!child) {
                me.body.appendChild(p);
                break;
              } else {
                child.parentNode.insertBefore(p, child);
                p = me.document.createElement("p");
              }
            }
            child = child.nextSibling;
          }
        }
      }
      me.fireEvent("aftersetcontent");
      me.fireEvent("contentchange");

      !notFireSelectionchange && me._selectionChange();
      //清除保存的选区
      me._bakRange = me._bakIERange = me._bakNativeRange = null;
      //trace:1742 setContent后gecko能得到焦点问题
      var geckoSel;
      if (browser.gecko && (geckoSel = this.selection.getNative())) {
        geckoSel.removeAllRanges();
      }
      if (me.options.autoSyncData) {
        me.form && setValue(me.form, me);
      }
    },

    /**
         * 让编辑器获得焦点，默认focus到编辑器头部
         * @method focus
         * @example
         * ```javascript
         * editor.focus()
         * ```
         */

    /**
         * 让编辑器获得焦点，toEnd确定focus位置
         * @method focus
         * @param { Boolean } toEnd 默认focus到编辑器头部，toEnd为true时focus到内容尾部
         * @example
         * ```javascript
         * editor.focus(true)
         * ```
         */
    focus: function(toEnd) {
      try {
        var me = this,
          rng = me.selection.getRange();
        if (toEnd) {
          var node = me.body.lastChild;
          if (node && node.nodeType == 1 && !dtd.$empty[node.tagName]) {
            if (domUtils.isEmptyBlock(node)) {
              rng.setStartAtFirst(node);
            } else {
              rng.setStartAtLast(node);
            }
            rng.collapse(true);
          }
          rng.setCursor(true);
        } else {
          if (
            !rng.collapsed &&
            domUtils.isBody(rng.startContainer) &&
            rng.startOffset == 0
          ) {
            var node = me.body.firstChild;
            if (node && node.nodeType == 1 && !dtd.$empty[node.tagName]) {
              rng.setStartAtFirst(node).collapse(true);
            }
          }

          rng.select(true);
        }
        this.fireEvent("focus selectionchange");
      } catch (e) {}
    },
    isFocus: function() {
      return this.selection.isFocus();
    },
    blur: function() {
      var sel = this.selection.getNative();
      if (sel.empty && browser.ie) {
        var nativeRng = document.body.createTextRange();
        nativeRng.moveToElementText(document.body);
        nativeRng.collapse(true);
        nativeRng.select();
        sel.empty();
      } else {
        sel.removeAllRanges();
      }

      //this.fireEvent('blur selectionchange');
    },
    /**
         * 初始化UE事件及部分事件代理
         * @method _initEvents
         * @private
         */
    _initEvents: function() {
      var me = this,
        doc = me.document,
        win = me.window;
      me._proxyDomEvent = utils.bind(me._proxyDomEvent, me);
      domUtils.on(
        doc,
        [
          "click",
          "contextmenu",
          "mousedown",
          "keydown",
          "keyup",
          "keypress",
          "mouseup",
          "mouseover",
          "mouseout",
          "selectstart"
        ],
        me._proxyDomEvent
      );
      domUtils.on(win, ["focus", "blur"], me._proxyDomEvent);
      domUtils.on(me.body, "drop", function(e) {
        //阻止ff下默认的弹出新页面打开图片
        if (browser.gecko && e.stopPropagation) {
          e.stopPropagation();
        }
        me.fireEvent("contentchange");
      });
      // 当内容最末尾为非字符时，比较难以在最后插入字符，所以在点击时，自动添加一个空的p标签
      domUtils.on(me.body, "dblclick", function(e) {
        try {
          var node = me.body.lastChild;
          if (!node) {
            return;
          }
          var rect = node.getBoundingClientRect();
          if (e.clientY > rect.top + rect.height) {
            var p = document.createElement('p');
            p.innerHTML = '<br />';
            me.body.appendChild(p);
            setTimeout(function () {
              me.focus(true);
            }, 100);
          }
        } catch (e) {
          console.error('auto insert p at end', e);
        }
      });
      domUtils.on(doc, ["mouseup", "keydown"], function(evt) {
        //特殊键不触发selectionchange
        if (
          evt.type === "keydown" &&
          (evt.ctrlKey || evt.metaKey || evt.shiftKey || evt.altKey)
        ) {
          return;
        }
        if (evt.button === 2) return;
        me._selectionChange(250, evt);
      });
    },
    /**
         * 触发事件代理
         * @method _proxyDomEvent
         * @private
         * @return { * } fireEvent的返回值
         * @see UE.EventBase:fireEvent(String)
         */
    _proxyDomEvent: function(evt) {
      if (
        this.fireEvent("before" + evt.type.replace(/^on/, "").toLowerCase()) ===
        false
      ) {
        return false;
      }
      if (this.fireEvent(evt.type.replace(/^on/, ""), evt) === false) {
        return false;
      }
      return this.fireEvent(
        "after" + evt.type.replace(/^on/, "").toLowerCase()
      );
    },
    /**
         * 变化选区
         * @method _selectionChange
         * @private
         */
    _selectionChange: function(delay, evt) {
      var me = this;
      //有光标才做selectionchange 为了解决未focus时点击source不能触发更改工具栏状态的问题（source命令notNeedUndo=1）
      //            if ( !me.selection.isFocus() ){
      //                return;
      //            }

      var hackForMouseUp = false;
      var mouseX, mouseY;
      if (browser.ie && browser.version < 9 && evt && evt.type == "mouseup") {
        var range = this.selection.getRange();
        if (!range.collapsed) {
          hackForMouseUp = true;
          mouseX = evt.clientX;
          mouseY = evt.clientY;
        }
      }
      clearTimeout(_selectionChangeTimer);
      _selectionChangeTimer = setTimeout(function() {
        if (!me.selection || !me.selection.getNative()) {
          return;
        }
        //修复一个IE下的bug: 鼠标点击一段已选择的文本中间时，可能在mouseup后的一段时间内取到的range是在selection的type为None下的错误值.
        //IE下如果用户是拖拽一段已选择文本，则不会触发mouseup事件，所以这里的特殊处理不会对其有影响
        var ieRange;
        if (hackForMouseUp && me.selection.getNative().type == "None") {
          ieRange = me.document.body.createTextRange();
          try {
            ieRange.moveToPoint(mouseX, mouseY);
          } catch (ex) {
            ieRange = null;
          }
        }
        var bakGetIERange;
        if (ieRange) {
          bakGetIERange = me.selection.getIERange;
          me.selection.getIERange = function() {
            return ieRange;
          };
        }
        me.selection.cache();
        if (bakGetIERange) {
          me.selection.getIERange = bakGetIERange;
        }
        if (me.selection._cachedRange && me.selection._cachedStartElement) {
          me.fireEvent("beforeselectionchange");
          // 第二个参数causeByUi为true代表由用户交互造成的selectionchange.
          me.fireEvent("selectionchange", !!evt);
          me.fireEvent("afterselectionchange");
          me.selection.clear();
        }
      }, delay || 50);
    },

    /**
         * 执行编辑命令
         * @method _callCmdFn
         * @private
         * @param { String } fnName 函数名称
         * @param { * } args 传给命令函数的参数
         * @return { * } 返回命令函数运行的返回值
         */
    _callCmdFn: function(fnName, args) {
      var cmdName = args[0].toLowerCase(),
        cmd,
        cmdFn;
      cmd = this.commands[cmdName] || UE.commands[cmdName];
      cmdFn = cmd && cmd[fnName];
      //没有querycommandstate或者没有command的都默认返回0
      if ((!cmd || !cmdFn) && fnName == "queryCommandState") {
        return 0;
      } else if (cmdFn) {
        return cmdFn.apply(this, args);
      }
    },

    /**
         * 执行编辑命令cmdName，完成富文本编辑效果
         * @method execCommand
         * @param { String } cmdName 需要执行的命令
         * @remind 具体命令的使用请参考<a href="#COMMAND.LIST">命令列表</a>
         * @return { * } 返回命令函数运行的返回值
         * @example
         * ```javascript
         * editor.execCommand(cmdName);
         * ```
         */
    execCommand: function(cmdName) {
      cmdName = cmdName.toLowerCase();
      var me = this,
        result,
        cmd = me.commands[cmdName] || UE.commands[cmdName];
      if (!cmd || !cmd.execCommand) {
        return null;
      }
      if (!cmd.notNeedUndo && !me.__hasEnterExecCommand) {
        me.__hasEnterExecCommand = true;
        if (me.queryCommandState.apply(me, arguments) != -1) {
          me.fireEvent("saveScene");
          me.fireEvent.apply(
            me,
            ["beforeexeccommand", cmdName].concat(arguments)
          );
          result = this._callCmdFn("execCommand", arguments);
          //保存场景时，做了内容对比，再看是否进行contentchange触发，这里多触发了一次，去掉
          //                    (!cmd.ignoreContentChange && !me._ignoreContentChange) && me.fireEvent('contentchange');
          me.fireEvent.apply(
            me,
            ["afterexeccommand", cmdName].concat(arguments)
          );
          me.fireEvent("saveScene");
        }
        me.__hasEnterExecCommand = false;
      } else {
        result = this._callCmdFn("execCommand", arguments);
        !me.__hasEnterExecCommand &&
          !cmd.ignoreContentChange &&
          !me._ignoreContentChange &&
          me.fireEvent("contentchange");
      }
      !me.__hasEnterExecCommand &&
        !cmd.ignoreContentChange &&
        !me._ignoreContentChange &&
        me._selectionChange();
      return result;
    },

    /**
         * 根据传入的command命令，查选编辑器当前的选区，返回命令的状态
         * @method  queryCommandState
         * @param { String } cmdName 需要查询的命令名称
         * @remind 具体命令的使用请参考<a href="#COMMAND.LIST">命令列表</a>
         * @return { Number } number 返回放前命令的状态，返回值三种情况：(-1|0|1)
         * @example
         * ```javascript
         * editor.queryCommandState(cmdName)  => (-1|0|1)
         * ```
         * @see COMMAND.LIST
         */
    queryCommandState: function(cmdName) {
      return this._callCmdFn("queryCommandState", arguments);
    },

    /**
         * 根据传入的command命令，查选编辑器当前的选区，根据命令返回相关的值
         * @method queryCommandValue
         * @param { String } cmdName 需要查询的命令名称
         * @remind 具体命令的使用请参考<a href="#COMMAND.LIST">命令列表</a>
         * @remind 只有部分插件有此方法
         * @return { * } 返回每个命令特定的当前状态值
         * @grammar editor.queryCommandValue(cmdName)  =>  {*}
         * @see COMMAND.LIST
         */
    queryCommandValue: function(cmdName) {
      return this._callCmdFn("queryCommandValue", arguments);
    },

    /**
         * 检查编辑区域中是否有内容
         * @method  hasContents
         * @remind 默认有文本内容，或者有以下节点都不认为是空
         * table,ul,ol,dl,iframe,area,base,col,hr,img,embed,input,link,meta,param
         * @return { Boolean } 检查有内容返回true，否则返回false
         * @example
         * ```javascript
         * editor.hasContents()
         * ```
         */

    /**
         * 检查编辑区域中是否有内容，若包含参数tags中的节点类型，直接返回true
         * @method  hasContents
         * @param { Array } tags 传入数组判断时用到的节点类型
         * @return { Boolean } 若文档中包含tags数组里对应的tag，返回true，否则返回false
         * @example
         * ```javascript
         * editor.hasContents(['span']);
         * ```
         */
    hasContents: function(tags) {
      if (tags) {
        for (var i = 0, ci; (ci = tags[i++]); ) {
          if (this.document.getElementsByTagName(ci).length > 0) {
            return true;
          }
        }
      }
      if (!domUtils.isEmptyBlock(this.body)) {
        return true;
      }
      // 随时添加,定义的特殊标签如果存在，不能认为是空
      tags = ["div"];
      for (i = 0; (ci = tags[i++]); ) {
        var nodes = domUtils.getElementsByTagName(this.document, ci);
        for (var n = 0, cn; (cn = nodes[n++]); ) {
          if (domUtils.isCustomeNode(cn)) {
            return true;
          }
        }
      }
      // 部分如媒体标签，不能认为为空
      tags = [ "video","iframe" ]
      for (i = 0; (ci = tags[i++]); ) {
        var nodes = domUtils.getElementsByTagName(this.document, ci);
        for (var n = 0, cn; (cn = nodes[n++]); ) {
          return true;
        }
      }
      return false;
    },

    /**
         * 重置编辑器，可用来做多个tab使用同一个编辑器实例
         * @method  reset
         * @remind 此方法会清空编辑器内容，清空回退列表，会触发reset事件
         * @example
         * ```javascript
         * editor.reset()
         * ```
         */
    reset: function() {
      this.fireEvent("reset");
    },

    /**
         * 设置当前编辑区域可以编辑
         * @method setEnabled
         * @example
         * ```javascript
         * editor.setEnabled()
         * ```
         */
    setEnabled: function() {
      var me = this,
        range;
      if (me.body.contentEditable === "false") {
        me.body.contentEditable = true;
        range = me.selection.getRange();
        //有可能内容丢失了
        try {
          range.moveToBookmark(me.lastBk);
          delete me.lastBk;
        } catch (e) {
          range.setStartAtFirst(me.body).collapse(true);
        }
        range.select(true);
        if (me.bkqueryCommandState) {
          me.queryCommandState = me.bkqueryCommandState;
          delete me.bkqueryCommandState;
        }
        if (me.bkqueryCommandValue) {
          me.queryCommandValue = me.bkqueryCommandValue;
          delete me.bkqueryCommandValue;
        }
        me.fireEvent("selectionchange");
      }
    },
    enable: function() {
      return this.setEnabled();
    },

    /** 设置当前编辑区域不可编辑
         * @method setDisabled
         */

    /** 设置当前编辑区域不可编辑,except中的命令除外
         * @method setDisabled
         * @param { String } except 例外命令的字符串
         * @remind 即使设置了disable，此处配置的例外命令仍然可以执行
         * @example
         * ```javascript
         * editor.setDisabled('bold'); //禁用工具栏中除加粗之外的所有功能
         * ```
         */

    /** 设置当前编辑区域不可编辑,except中的命令除外
         * @method setDisabled
         * @param { Array } except 例外命令的字符串数组，数组中的命令仍然可以执行
         * @remind 即使设置了disable，此处配置的例外命令仍然可以执行
         * @example
         * ```javascript
         * editor.setDisabled(['bold','insertimage']); //禁用工具栏中除加粗和插入图片之外的所有功能
         * ```
         */
    setDisabled: function(except) {
      var me = this;
      except = except ? (utils.isArray(except) ? except : [except]) : [];
      if (me.body.contentEditable == "true") {
        if (!me.lastBk) {
          me.lastBk = me.selection.getRange().createBookmark(true);
        }
        me.body.contentEditable = false;
        me.bkqueryCommandState = me.queryCommandState;
        me.bkqueryCommandValue = me.queryCommandValue;
        me.queryCommandState = function(type) {
          if (utils.indexOf(except, type) != -1) {
            return me.bkqueryCommandState.apply(me, arguments);
          }
          return -1;
        };
        me.queryCommandValue = function(type) {
          if (utils.indexOf(except, type) != -1) {
            return me.bkqueryCommandValue.apply(me, arguments);
          }
          return null;
        };
        me.fireEvent("selectionchange");
      }
    },
    disable: function(except) {
      return this.setDisabled(except);
    },

    /**
         * 设置默认内容
         * @method _setDefaultContent
         * @private
         * @param  { String } cont 要存入的内容
         */
    _setDefaultContent: (function() {
      function clear() {
        var me = this;
        if (me.document.getElementById("initContent")) {
          me.body.innerHTML = "<p>" + (ie ? "" : "<br/>") + "</p>";
          me.removeListener("firstBeforeExecCommand focus", clear);
          setTimeout(function() {
            me.focus();
            me._selectionChange();
          }, 0);
        }
      }

      return function(cont) {
        var me = this;
        me.body.innerHTML = '<p id="initContent">' + cont + "</p>";

        me.addListener("firstBeforeExecCommand focus", clear);
      };
    })(),

    /**
         * 显示编辑器
         * @method setShow
         * @example
         * ```javascript
         * editor.setShow()
         * ```
         */
    setShow: function() {
      var me = this,
        range = me.selection.getRange();
      if (me.container.style.display == "none") {
        //有可能内容丢失了
        try {
          range.moveToBookmark(me.lastBk);
          delete me.lastBk;
        } catch (e) {
          range.setStartAtFirst(me.body).collapse(true);
        }
        //ie下focus实效，所以做了个延迟
        setTimeout(function() {
          range.select(true);
        }, 100);
        me.container.style.display = "";
      }
    },
    show: function() {
      return this.setShow();
    },
    /**
         * 隐藏编辑器
         * @method setHide
         * @example
         * ```javascript
         * editor.setHide()
         * ```
         */
    setHide: function() {
      var me = this;
      if (!me.lastBk) {
        me.lastBk = me.selection.getRange().createBookmark(true);
      }
      me.container.style.display = "none";
    },
    hide: function() {
      return this.setHide();
    },

    /**
         * 根据指定的路径，获取对应的语言资源
         * @method getLang
         * @param { String } path 路径根据的是lang目录下的语言文件的路径结构
         * @return { Object | String } 根据路径返回语言资源的Json格式对象或者语言字符串
         * @example
         * ```javascript
         * editor.getLang('contextMenu.delete'); //如果当前是中文，那返回是的是'删除'
         * ```
         */
    getLang: function(path) {
      var lang = UE.I18N[this.options.lang];
      if (!lang) {
        throw Error("not import language file");
      }
      path = (path || "").split(".");
      for (var i = 0, ci; (ci = path[i++]); ) {
        lang = lang[ci];
        if (!lang) break;
      }
      return lang;
    },

    /**
         * 计算编辑器html内容字符串的长度
         * @method  getContentLength
         * @return { Number } 返回计算的长度
         * @example
         * ```javascript
         * //编辑器html内容<p><strong>132</strong></p>
         * editor.getContentLength() //返回27
         * ```
         */
    /**
         * 计算编辑器当前纯文本内容的长度
         * @method  getContentLength
         * @param { Boolean } ingoneHtml 传入true时，只按照纯文本来计算
         * @return { Number } 返回计算的长度，内容中有hr/img/iframe标签，长度加1
         * @example
         * ```javascript
         * //编辑器html内容<p><strong>132</strong></p>
         * editor.getContentLength() //返回3
         * ```
         */
    getContentLength: function(ingoneHtml, tagNames) {
      var count = this.getContent(false, false, true).length;
      if (ingoneHtml) {
        tagNames = (tagNames || []).concat(["hr", "img", "iframe"]);
        count = this.getContentTxt().replace(/[\t\r\n]+/g, "").length;
        for (var i = 0, ci; (ci = tagNames[i++]); ) {
          count += this.document.getElementsByTagName(ci).length;
        }
      }
      return count;
    },

    /**
         * 注册输入过滤规则
         * @method  addInputRule
         * @param { Function } rule 要添加的过滤规则
         * @example
         * ```javascript
         * editor.addInputRule(function(root){
         *   $.each(root.getNodesByTagName('div'),function(i,node){
         *       node.tagName="p";
         *   });
         * });
         * ```
         */
    addInputRule: function(rule) {
      this.inputRules.push(rule);
    },

    /**
         * 执行注册的过滤规则
         * @method  filterInputRule
         * @param { UE.uNode } root 要过滤的uNode节点
         * @remind 执行editor.setContent方法和执行'inserthtml'命令后，会运行该过滤函数
         * @example
         * ```javascript
         * editor.filterInputRule(editor.body);
         * ```
         * @see UE.Editor:addInputRule
         */
    filterInputRule: function(root) {
      for (var i = 0, ci; (ci = this.inputRules[i++]); ) {
        ci.call(this, root);
      }
    },

    /**
         * 注册输出过滤规则
         * @method  addOutputRule
         * @param { Function } rule 要添加的过滤规则
         * @example
         * ```javascript
         * editor.addOutputRule(function(root){
         *   $.each(root.getNodesByTagName('p'),function(i,node){
         *       node.tagName="div";
         *   });
         * });
         * ```
         */
    addOutputRule: function(rule) {
      this.outputRules.push(rule);
    },

    /**
         * 根据输出过滤规则，过滤编辑器内容
         * @method  filterOutputRule
         * @remind 执行editor.getContent方法的时候，会先运行该过滤函数
         * @param { UE.uNode } root 要过滤的uNode节点
         * @example
         * ```javascript
         * editor.filterOutputRule(editor.body);
         * ```
         * @see UE.Editor:addOutputRule
         */
    filterOutputRule: function(root) {
      for (var i = 0, ci; (ci = this.outputRules[i++]); ) {
        ci.call(this, root);
      }
    },

    /**
         * 根据action名称获取请求的路径
         * @method  getActionUrl
         * @remind 假如没有设置serverUrl,会根据imageUrl设置默认的controller路径
         * @param { String } action action名称
         * @example
         * ```javascript
         * editor.getActionUrl('config'); //返回 "/ueditor/php/controller.php?action=config"
         * editor.getActionUrl('image'); //返回 "/ueditor/php/controller.php?action=uplaodimage"
         * editor.getActionUrl('scrawl'); //返回 "/ueditor/php/controller.php?action=uplaodscrawl"
         * editor.getActionUrl('imageManager'); //返回 "/ueditor/php/controller.php?action=listimage"
         * ```
         */
    getActionUrl: function(action) {
      var actionName = this.getOpt(action) || action,
        imageUrl = this.getOpt("imageUrl"),
        serverUrl = this.getOpt("serverUrl");

      if (!serverUrl && imageUrl) {
        serverUrl = imageUrl.replace(/^(.*[\/]).+([\.].+)$/, "$1controller$2");
      }

      if (serverUrl) {
        serverUrl =
          serverUrl +
          (serverUrl.indexOf("?") == -1 ? "?" : "&") +
          "action=" +
          (actionName || "");
        return utils.formatUrl(serverUrl);
      } else {
        return "";
      }
    }
  };
  utils.inherits(Editor, EventBase);
})();


// core/Editor.defaultoptions.js
//维护编辑器一下默认的不在插件中的配置项
UE.Editor.defaultOptions = function(editor) {
  var _url = editor.options.UEDITOR_HOME_URL;
  return {
    isShow: true,
    initialContent: "",
    initialStyle: "",
    autoClearinitialContent: false,
    iframeCssUrl: _url + "themes/iframe.css?20221113",
      iframeCssUrlsAddition: [],
    textarea: "editorValue",
    focus: false,
    focusInEnd: true,
    autoClearEmptyNode: true,
    fullscreen: false,
    readonly: false,
    zIndex: 999,
    imagePopup: true,
    enterTag: "p",
    customDomain: false,
    lang: "zh-cn",
    langPath: _url + "lang/",
    theme: "default",
    themePath: _url + "themes/",
    allHtmlEnabled: false,
    scaleEnabled: false,
    tableNativeEditInFF: false,
    autoSyncData: true,
    fileNameFormat: "{time}{rand:6}"
  };
};


// core/loadconfig.js
(function() {
  UE.Editor.prototype.loadServerConfig = function() {
    var me = this;
    setTimeout(function() {
      try {
        me.options.imageUrl &&
          me.setOpt(
            "serverUrl",
            me.options.imageUrl.replace(
              /^(.*[\/]).+([\.].+)$/,
              "$1controller$2"
            )
          );

        var configUrl = me.getActionUrl("config"),
          isJsonp = utils.isCrossDomainUrl(configUrl);

        /* 发出ajax请求 */
        me._serverConfigLoaded = false;

        configUrl &&
          UE.ajax.request(configUrl, {
            method: "GET",
            dataType: isJsonp ? "jsonp" : "",
            headers: me.options.serverHeaders || {},
            onsuccess: function(r) {
              try {
                var config = isJsonp ? r : eval("(" + r.responseText + ")");
                utils.extend(me.options, config);
                // console.log('me.options.before',me.options);
                // console.log('server.config',config);
                // console.log('me.options.after',me.options);
                me.fireEvent("serverConfigLoaded");
                me._serverConfigLoaded = true;
              } catch (e) {
                showErrorMsg(me.getLang("loadconfigFormatError"));
              }
            },
            onerror: function() {
              showErrorMsg(me.getLang("loadconfigHttpError"));
            }
          });
      } catch (e) {
        showErrorMsg(me.getLang("loadconfigError"));
      }
    });

    function showErrorMsg(msg) {
      console && console.error(msg);
      //me.fireEvent('showMessage', {
      //    'title': msg,
      //    'type': 'error'
      //});
    }
  };

  UE.Editor.prototype.isServerConfigLoaded = function() {
    var me = this;
    return me._serverConfigLoaded || false;
  };

  UE.Editor.prototype.afterConfigReady = function(handler) {
    if (!handler || !utils.isFunction(handler)) return;
    var me = this;
    var readyHandler = function() {
      handler.apply(me, arguments);
      me.removeListener("serverConfigLoaded", readyHandler);
    };

    if (me.isServerConfigLoaded()) {
      handler.call(me, "serverConfigLoaded");
    } else {
      me.addListener("serverConfigLoaded", readyHandler);
    }
  };
})();


// core/ajax.js
/**
 * @file
 * @module UE.ajax
 * @since 1.2.6.1
 */

/**
 * 提供对ajax请求的支持
 * @module UE.ajax
 */
UE.ajax = (function() {
  //创建一个ajaxRequest对象
  var fnStr = "XMLHttpRequest()";
  try {
    new ActiveXObject("Msxml2.XMLHTTP");
    fnStr = "ActiveXObject('Msxml2.XMLHTTP')";
  } catch (e) {
    try {
      new ActiveXObject("Microsoft.XMLHTTP");
      fnStr = "ActiveXObject('Microsoft.XMLHTTP')";
    } catch (e) {}
  }
  var creatAjaxRequest = new Function("return new " + fnStr);

  /**
     * 将json参数转化成适合ajax提交的参数列表
     * @param json
     */
  function json2str(json) {
    var strArr = [];
    for (var i in json) {
      //忽略默认的几个参数
      if (
        i == "method" ||
        i == "timeout" ||
        i == "async" ||
        i == "dataType" ||
        i == "callback"
      )
        continue;
      //忽略控制
      if (json[i] == undefined || json[i] == null) continue;
      //传递过来的对象和函数不在提交之列
      if (
        !(
          (typeof json[i]).toLowerCase() == "function" ||
          (typeof json[i]).toLowerCase() == "object"
        )
      ) {
        strArr.push(encodeURIComponent(i) + "=" + encodeURIComponent(json[i]));
      } else if (utils.isArray(json[i])) {
        //支持传数组内容
        for (var j = 0; j < json[i].length; j++) {
          strArr.push(
            encodeURIComponent(i) + "[]=" + encodeURIComponent(json[i][j])
          );
        }
      }
    }
    return strArr.join("&");
  }

  function doAjax(url, ajaxOptions) {
    var xhr = creatAjaxRequest(),
      //是否超时
      timeIsOut = false,
      //默认参数
      defaultAjaxOptions = {
        method: "POST",
        timeout: 5000,
        async: true,
        headers: {},
        data: {}, //需要传递对象的话只能覆盖
        onsuccess: function() {},
        onerror: function() {}
      };

    if (typeof url === "object") {
      ajaxOptions = url;
      url = ajaxOptions.url;
    }
    if (!xhr || !url) return;
    var ajaxOpts = ajaxOptions
      ? utils.extend(defaultAjaxOptions, ajaxOptions)
      : defaultAjaxOptions;

    // console.log('ajaxOpts',ajaxOpts);

    var submitStr = json2str(ajaxOpts); // { name:"Jim",city:"Beijing" } --> "name=Jim&city=Beijing"
    //如果用户直接通过data参数传递json对象过来，则也要将此json对象转化为字符串
    if (!utils.isEmptyObject(ajaxOpts.data)) {
      submitStr += (submitStr ? "&" : "") + json2str(ajaxOpts.data);
    }
    //超时检测
    var timerID = setTimeout(function() {
      if (xhr.readyState !== 4) {
        timeIsOut = true;
        xhr.abort();
        clearTimeout(timerID);
      }
    }, ajaxOpts.timeout);

    var method = ajaxOpts.method.toUpperCase();
    var str =
      url +
      (url.indexOf("?") === -1 ? "?" : "&") +
      (method === "POST" ? "" : submitStr + "&noCache=" + +new Date());
    xhr.open(method, str, ajaxOpts.async);
    xhr.onreadystatechange = function() {
      if (xhr.readyState === 4) {
        if (!timeIsOut && xhr.status === 200) {
          ajaxOpts.onsuccess(xhr);
        } else {
          ajaxOpts.onerror(xhr);
        }
      }
    };
    if(ajaxOpts.headers){
      for(var key in ajaxOpts.headers){
        xhr.setRequestHeader(key,ajaxOpts.headers[key]);
      }
    }
    if (method === "POST") {
      xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
      xhr.send(submitStr);
    } else {
      xhr.send(null);
    }
  }

  function doJsonp(url, opts) {
    var successhandler = opts.onsuccess || function() {},
      scr = document.createElement("SCRIPT"),
      options = opts || {},
      charset = options["charset"],
      callbackField = options["jsonp"] || "callback",
      callbackFnName,
      timeOut = options["timeOut"] || 0,
      timer,
      reg = new RegExp("(\\?|&)" + callbackField + "=([^&]*)"),
      matches;

    if (utils.isFunction(successhandler)) {
      callbackFnName =
        "bd__editor__" + Math.floor(Math.random() * 2147483648).toString(36);
      window[callbackFnName] = getCallBack(0);
    } else if (utils.isString(successhandler)) {
      callbackFnName = successhandler;
    } else {
      if ((matches = reg.exec(url))) {
        callbackFnName = matches[2];
      }
    }

    url = url.replace(reg, "\x241" + callbackField + "=" + callbackFnName);

    if (url.search(reg) < 0) {
      url +=
        (url.indexOf("?") < 0 ? "?" : "&") +
        callbackField +
        "=" +
        callbackFnName;
    }

    var queryStr = json2str(opts); // { name:"Jim",city:"Beijing" } --> "name=Jim&city=Beijing"
    //如果用户直接通过data参数传递json对象过来，则也要将此json对象转化为字符串
    if (!utils.isEmptyObject(opts.data)) {
      queryStr += (queryStr ? "&" : "") + json2str(opts.data);
    }
    if (queryStr) {
      url = url.replace(/\?/, "?" + queryStr + "&");
    }

    scr.onerror = getCallBack(1);
    if (timeOut) {
      timer = setTimeout(getCallBack(1), timeOut);
    }
    createScriptTag(scr, url, charset);

    function createScriptTag(scr, url, charset) {
      scr.setAttribute("type", "text/javascript");
      scr.setAttribute("defer", "defer");
      charset && scr.setAttribute("charset", charset);
      scr.setAttribute("src", url);
      document.getElementsByTagName("head")[0].appendChild(scr);
    }

    function getCallBack(onTimeOut) {
      return function() {
        try {
          if (onTimeOut) {
            options.onerror && options.onerror();
          } else {
            try {
              clearTimeout(timer);
              successhandler.apply(window, arguments);
            } catch (e) {}
          }
        } catch (exception) {
          options.onerror && options.onerror.call(window, exception);
        } finally {
          options.oncomplete && options.oncomplete.apply(window, arguments);
          scr.parentNode && scr.parentNode.removeChild(scr);
          window[callbackFnName] = null;
          try {
            delete window[callbackFnName];
          } catch (e) {}
        }
      };
    }
  }

  return {
    /**
         * 根据给定的参数项，向指定的url发起一个ajax请求。 ajax请求完成后，会根据请求结果调用相应回调： 如果请求
         * 成功， 则调用onsuccess回调， 失败则调用 onerror 回调
         * @method request
         * @param { URLString } url ajax请求的url地址
         * @param { Object } ajaxOptions ajax请求选项的键值对，支持的选项如下：
         * @example
         * ```javascript
         * //向sayhello.php发起一个异步的Ajax GET请求, 请求超时时间为10s， 请求完成后执行相应的回调。
         * UE.ajax.requeset( 'sayhello.php', {
         *
         *     //请求方法。可选值： 'GET', 'POST'，默认值是'POST'
         *     method: 'GET',
         *
         *     //超时时间。 默认为5000， 单位是ms
         *     timeout: 10000,
         *
         *     //是否是异步请求。 true为异步请求， false为同步请求
         *     async: true,
         *
         *     //请求携带的数据。如果请求为GET请求， data会经过stringify后附加到请求url之后。
         *     data: {
         *         name: 'ueditor'
         *     },
         *
         *     //请求成功后的回调， 该回调接受当前的XMLHttpRequest对象作为参数。
         *     onsuccess: function ( xhr ) {
         *         console.log( xhr.responseText );
         *     },
         *
         *     //请求失败或者超时后的回调。
         *     onerror: function ( xhr ) {
         *          alert( 'Ajax请求失败' );
         *     }
         *
         * } );
         * ```
         */

    /**
         * 根据给定的参数项发起一个ajax请求， 参数项里必须包含一个url地址。 ajax请求完成后，会根据请求结果调用相应回调： 如果请求
         * 成功， 则调用onsuccess回调， 失败则调用 onerror 回调。
         * @method request
         * @warning 如果在参数项里未提供一个key为“url”的地址值，则该请求将直接退出。
         * @param { Object } ajaxOptions ajax请求选项的键值对，支持的选项如下：
         * @example
         * ```javascript
         *
         * //向sayhello.php发起一个异步的Ajax POST请求, 请求超时时间为5s， 请求完成后不执行任何回调。
         * UE.ajax.requeset( 'sayhello.php', {
         *
         *     //请求的地址， 该项是必须的。
         *     url: 'sayhello.php'
         *
         * } );
         * ```
         */
    request: function(url, opts) {
      if (opts && opts.dataType === "jsonp") {
        doJsonp(url, opts);
      } else {
        doAjax(url, opts);
      }
    },
    getJSONP: function(url, data, fn) {
      var opts = {
        data: data,
        oncomplete: fn
      };
      doJsonp(url, opts);
    }
  };
})();


// core/filterword.js
/**
 * UE过滤word的静态方法
 * @file
 */

/**
 * UEditor公用空间，UEditor所有的功能都挂载在该空间下
 * @module UE
 */

/**
 * 根据传入html字符串过滤word
 * @module UE
 * @since 1.2.6.1
 * @method filterWord
 * @param { String } html html字符串
 * @return { String } 已过滤后的结果字符串
 * @example
 * ```javascript
 * UE.filterWord(html);
 * ```
 */
var filterWord = (UE.filterWord = (function() {
  //是否是word过来的内容
  function isWordDocument(str) {
    return /(class="?Mso|style="[^"]*\bmso\-|w:WordDocument|<(v|o):|lang=)/gi.test(
      str
    );
  }
  //去掉小数
  function transUnit(v) {
    v = v.replace(/[\d.]+\w+/g, function(m) {
      return utils.transUnitToPx(m);
    });
    return v;
  }

  function filterPasteWord(str) {
    return (
      str
        .replace(/[\t\r\n]+/g, " ")
        .replace(/<!--[\s\S]*?-->/gi, "")
        //转换图片
        .replace(/<v:shape [^>]*>[\s\S]*?.<\/v:shape>/gi, function(str) {
          //opera能自己解析出image所这里直接返回空
          if (browser.opera) {
            return "";
          }
          try {
            //有可能是bitmap占为图，无用，直接过滤掉，主要体现在粘贴excel表格中
            if (/Bitmap/i.test(str)) {
              return "";
            }
            var width = str.match(/width:([ \d.]*p[tx])/i)[1],
              height = str.match(/height:([ \d.]*p[tx])/i)[1],
              src = str.match(/src=\s*"([^"]*)"/i)[1];
            return (
              '<img width="' +
              transUnit(width) +
              '" height="' +
              transUnit(height) +
              '" src="' +
              src +
              '" />'
            );
          } catch (e) {
            return "";
          }
        })
        //针对wps添加的多余标签处理
        .replace(/<\/?div[^>]*>/g, "")
        //去掉多余的属性
        .replace(/v:\w+=(["']?)[^'"]+\1/g, "")
        .replace(
          /<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|xml|meta|link|style|\w+:\w+)(?=[\s\/>]))[^>]*>/gi,
          ""
        )
        .replace(
          /<p [^>]*class="?MsoHeading"?[^>]*>(.*?)<\/p>/gi,
          "<p><strong>$1</strong></p>"
        )
        //去掉多余的属性
        .replace(/\s+(class|lang|align)\s*=\s*(['"]?)([\w-]+)\2/gi, function(
          str,
          name,
          marks,
          val
        ) {
          //保留list的标示
          return name == "class" && val == "MsoListParagraph" ? str : "";
        })
        //清除多余的font/span不能匹配&nbsp;有可能是空格
        .replace(/<(font|span)[^>]*>(\s*)<\/\1>/gi, function(a, b, c) {
          return c.replace(/[\t\r\n ]+/g, " ");
        })
        //处理style的问题
        .replace(/(<[a-z][^>]*)\sstyle=(["'])([^\2]*?)\2/gi, function(
          str,
          tag,
          tmp,
          style
        ) {
          var n = [],
            s = style
              .replace(/^\s+|\s+$/, "")
              .replace(/&#39;/g, "'")
              .replace(/&quot;/gi, "'")
              .replace(/[\d.]+(cm|pt)/g, function(str) {
                return utils.transUnitToPx(str);
              })
              .split(/;\s*/g);

          for (var i = 0, v; (v = s[i]); i++) {
            var name,
              value,
              parts = v.split(":");

            if (parts.length == 2) {
              name = parts[0].toLowerCase();
              value = parts[1].toLowerCase();
              if (
                (/^(background)\w*/.test(name) &&
                  value.replace(/(initial|\s)/g, "").length == 0) ||
                (/^(margin)\w*/.test(name) && /^0\w+$/.test(value))
              ) {
                continue;
              }

              switch (name) {
                case "mso-padding-alt":
                case "mso-padding-top-alt":
                case "mso-padding-right-alt":
                case "mso-padding-bottom-alt":
                case "mso-padding-left-alt":
                case "mso-margin-alt":
                case "mso-margin-top-alt":
                case "mso-margin-right-alt":
                case "mso-margin-bottom-alt":
                case "mso-margin-left-alt":
                //ie下会出现挤到一起的情况
                //case "mso-table-layout-alt":
                case "mso-height":
                case "mso-width":
                case "mso-vertical-align-alt":
                  //trace:1819 ff下会解析出padding在table上
                  if (!/<table/.test(tag))
                    n[i] =
                      name.replace(/^mso-|-alt$/g, "") + ":" + transUnit(value);
                  continue;
                case "horiz-align":
                  n[i] = "text-align:" + value;
                  continue;

                case "vert-align":
                  n[i] = "vertical-align:" + value;
                  continue;

                case "font-color":
                case "mso-foreground":
                  n[i] = "color:" + value;
                  continue;

                case "mso-background":
                case "mso-highlight":
                  n[i] = "background:" + value;
                  continue;

                case "mso-default-height":
                  n[i] = "min-height:" + transUnit(value);
                  continue;

                case "mso-default-width":
                  n[i] = "min-width:" + transUnit(value);
                  continue;

                case "mso-padding-between-alt":
                  n[i] =
                    "border-collapse:separate;border-spacing:" +
                    transUnit(value);
                  continue;

                case "text-line-through":
                  if (value == "single" || value == "double") {
                    n[i] = "text-decoration:line-through";
                  }
                  continue;
                case "mso-zero-height":
                  if (value == "yes") {
                    n[i] = "display:none";
                  }
                  continue;
                //                                case 'background':
                //                                    break;
                case "margin":
                  if (!/[1-9]/.test(value)) {
                    continue;
                  }
              }

              if (
                /^(mso|column|font-emph|lang|layout|line-break|list-image|nav|panose|punct|row|ruby|sep|size|src|tab-|table-border|text-(?:decor|trans)|top-bar|version|vnd|word-break)/.test(
                  name
                ) ||
                (/text\-indent|padding|margin/.test(name) &&
                  /\-[\d.]+/.test(value))
              ) {
                continue;
              }

              n[i] = name + ":" + parts[1];
            }
          }
          return (
            tag +
            (n.length
              ? ' style="' + n.join(";").replace(/;{2,}/g, ";") + '"'
              : "")
          );
        })
    );
  }

  return function(html) {
    return isWordDocument(html) ? filterPasteWord(html) : html;
  };
})());


// core/node.js
/**
 * 编辑器模拟的节点类
 * @file
 * @module UE
 * @class uNode
 * @since 1.2.6.1
 */

/**
 * UEditor公用空间，UEditor所有的功能都挂载在该空间下
 * @unfile
 * @module UE
 */

(function() {
  /**
     * 编辑器模拟的节点类
     * @unfile
     * @module UE
     * @class uNode
     */

  /**
     * 通过一个键值对，创建一个uNode对象
     * @constructor
     * @param { Object } attr 传入要创建的uNode的初始属性
     * @example
     * ```javascript
     * var node = new uNode({
     *     type:'element',
     *     tagName:'span',
     *     attrs:{style:'font-size:14px;'}
     * })
     * ```
     */
  var uNode = (UE.uNode = function(obj) {
    this.type = obj.type;
    this.data = obj.data;
    this.tagName = obj.tagName;
    this.parentNode = obj.parentNode;
    this.attrs = obj.attrs || {};
    this.children = obj.children;
  });

  var notTransAttrs = {
    href: 1,
    src: 1,
    _src: 1,
    _href: 1,
    cdata_data: 1
  };

  var notTransTagName = {
    style: 1,
    script: 1
  };

  var indentChar = "    ",
    breakChar = "\n";

  function insertLine(arr, current, begin) {
    arr.push(breakChar);
    return current + (begin ? 1 : -1);
  }

  function insertIndent(arr, current) {
    //插入缩进
    for (var i = 0; i < current; i++) {
      arr.push(indentChar);
    }
  }

  //创建uNode的静态方法
  //支持标签和html
  uNode.createElement = function(html) {
    if (/[<>]/.test(html)) {
      return UE.htmlparser(html).children[0];
    } else {
      return new uNode({
        type: "element",
        children: [],
        tagName: html
      });
    }
  };
  uNode.createText = function(data, noTrans) {
    return new UE.uNode({
      type: "text",
      data: noTrans ? data : utils.unhtml(data || "")
    });
  };
  function nodeToHtml(node, arr, formatter, current) {
    switch (node.type) {
      case "root":
        for (var i = 0, ci; (ci = node.children[i++]); ) {
          //插入新行
          if (
            formatter &&
            ci.type == "element" &&
            !dtd.$inlineWithA[ci.tagName] &&
            i > 1
          ) {
            insertLine(arr, current, true);
            insertIndent(arr, current);
          }
          nodeToHtml(ci, arr, formatter, current);
        }
        break;
      case "text":
        isText(node, arr);
        break;
      case "element":
        isElement(node, arr, formatter, current);
        break;
      case "comment":
        isComment(node, arr, formatter);
    }
    return arr;
  }

  function isText(node, arr) {
    if (node.parentNode.tagName == "pre") {
      //源码模式下输入html标签，不能做转换处理，直接输出
      arr.push(node.data);
    } else {
      arr.push(
        notTransTagName[node.parentNode.tagName]
          ? utils.html(node.data)
          : node.data.replace(/[ ]{2}/g, " &nbsp;")
      );
    }
  }

  function isElement(node, arr, formatter, current) {
    var attrhtml = "";
    if (node.attrs) {
      attrhtml = [];
      var attrs = node.attrs;
      for (var a in attrs) {
        //这里就针对
        //<p>'<img src='http://nsclick.baidu.com/u.gif?&asdf=\"sdf&asdfasdfs;asdf'></p>
        //这里边的\"做转换，要不用innerHTML直接被截断了，属性src
        //有可能做的不够
        attrhtml.push(
          a +
            (attrs[a] !== undefined
              ? '="' +
                  (notTransAttrs[a]
                    ? utils.html(attrs[a]).replace(/["]/g, function(a) {
                        return "&quot;";
                      })
                    : utils.unhtml(attrs[a])) +
                  '"'
              : "")
        );
      }
      attrhtml = attrhtml.join(" ");
    }
    arr.push(
      "<" +
        node.tagName +
        (attrhtml ? " " + attrhtml : "") +
        (dtd.$empty[node.tagName] ? "/" : "") +
        ">"
    );
    //插入新行
    if (formatter && !dtd.$inlineWithA[node.tagName] && node.tagName != "pre") {
      if (node.children && node.children.length) {
        current = insertLine(arr, current, true);
        insertIndent(arr, current);
      }
    }
    if (node.children && node.children.length) {
      for (var i = 0, ci; (ci = node.children[i++]); ) {
        if (
          formatter &&
          ci.type == "element" &&
          !dtd.$inlineWithA[ci.tagName] &&
          i > 1
        ) {
          insertLine(arr, current);
          insertIndent(arr, current);
        }
        nodeToHtml(ci, arr, formatter, current);
      }
    }
    if (!dtd.$empty[node.tagName]) {
      if (
        formatter &&
        !dtd.$inlineWithA[node.tagName] &&
        node.tagName != "pre"
      ) {
        if (node.children && node.children.length) {
          current = insertLine(arr, current);
          insertIndent(arr, current);
        }
      }
      arr.push("</" + node.tagName + ">");
    }
  }

  function isComment(node, arr) {
    arr.push("<!--" + node.data + "-->");
  }

  function getNodeById(root, id) {
    var node;
    if (root.type == "element" && root.getAttr("id") == id) {
      return root;
    }
    if (root.children && root.children.length) {
      for (var i = 0, ci; (ci = root.children[i++]); ) {
        if ((node = getNodeById(ci, id))) {
          return node;
        }
      }
    }
  }

  function getNodesByTagName(node, tagName, arr) {
    if (node.type == "element" && node.tagName == tagName) {
      arr.push(node);
    }
    if (node.children && node.children.length) {
      for (var i = 0, ci; (ci = node.children[i++]); ) {
        getNodesByTagName(ci, tagName, arr);
      }
    }
  }
  function nodeTraversal(root, fn) {
    if (root.children && root.children.length) {
      for (var i = 0, ci; (ci = root.children[i]); ) {
        nodeTraversal(ci, fn);
        //ci被替换的情况，这里就不再走 fn了
        if (ci.parentNode) {
          if (ci.children && ci.children.length) {
            fn(ci);
          }
          if (ci.parentNode) i++;
        }
      }
    } else {
      fn(root);
    }
  }
  uNode.prototype = {
    /**
         * 当前节点对象，转换成html文本
         * @method toHtml
         * @return { String } 返回转换后的html字符串
         * @example
         * ```javascript
         * node.toHtml();
         * ```
         */

    /**
         * 当前节点对象，转换成html文本
         * @method toHtml
         * @param { Boolean } formatter 是否格式化返回值
         * @return { String } 返回转换后的html字符串
         * @example
         * ```javascript
         * node.toHtml( true );
         * ```
         */
    toHtml: function(formatter) {
      var arr = [];
      nodeToHtml(this, arr, formatter, 0);
      return arr.join("");
    },

    /**
         * 获取节点的html内容
         * @method innerHTML
         * @warning 假如节点的type不是'element'，或节点的标签名称不在dtd列表里，直接返回当前节点
         * @return { String } 返回节点的html内容
         * @example
         * ```javascript
         * var htmlstr = node.innerHTML();
         * ```
         */

    /**
         * 设置节点的html内容
         * @method innerHTML
         * @warning 假如节点的type不是'element'，或节点的标签名称不在dtd列表里，直接返回当前节点
         * @param { String } htmlstr 传入要设置的html内容
         * @return { UE.uNode } 返回节点本身
         * @example
         * ```javascript
         * node.innerHTML('<span>text</span>');
         * ```
         */
    innerHTML: function(htmlstr) {
      if (this.type != "element" || dtd.$empty[this.tagName]) {
        return this;
      }
      if (utils.isString(htmlstr)) {
        if (this.children) {
          for (var i = 0, ci; (ci = this.children[i++]); ) {
            ci.parentNode = null;
          }
        }
        this.children = [];
        var tmpRoot = UE.htmlparser(htmlstr);
        for (var i = 0, ci; (ci = tmpRoot.children[i++]); ) {
          this.children.push(ci);
          ci.parentNode = this;
        }
        return this;
      } else {
        var tmpRoot = new UE.uNode({
          type: "root",
          children: this.children
        });
        return tmpRoot.toHtml();
      }
    },

    /**
         * 获取节点的纯文本内容
         * @method innerText
         * @warning 假如节点的type不是'element'，或节点的标签名称不在dtd列表里，直接返回当前节点
         * @return { String } 返回节点的存文本内容
         * @example
         * ```javascript
         * var textStr = node.innerText();
         * ```
         */

    /**
         * 设置节点的纯文本内容
         * @method innerText
         * @warning 假如节点的type不是'element'，或节点的标签名称不在dtd列表里，直接返回当前节点
         * @param { String } textStr 传入要设置的文本内容
         * @return { UE.uNode } 返回节点本身
         * @example
         * ```javascript
         * node.innerText('<span>text</span>');
         * ```
         */
    innerText: function(textStr, noTrans) {
      if (this.type != "element" || dtd.$empty[this.tagName]) {
        return this;
      }
      if (textStr) {
        if (this.children) {
          for (var i = 0, ci; (ci = this.children[i++]); ) {
            ci.parentNode = null;
          }
        }
        this.children = [];
        this.appendChild(uNode.createText(textStr, noTrans));
        return this;
      } else {
        return this.toHtml().replace(/<[^>]+>/g, "");
      }
    },

    /**
         * 获取当前对象的data属性
         * @method getData
         * @return { Object } 若节点的type值是elemenet，返回空字符串，否则返回节点的data属性
         * @example
         * ```javascript
         * node.getData();
         * ```
         */
    getData: function() {
      if (this.type == "element") return "";
      return this.data;
    },

    /**
         * 获取当前节点下的第一个子节点
         * @method firstChild
         * @return { UE.uNode } 返回第一个子节点
         * @example
         * ```javascript
         * node.firstChild(); //返回第一个子节点
         * ```
         */
    firstChild: function() {
      //            if (this.type != 'element' || dtd.$empty[this.tagName]) {
      //                return this;
      //            }
      return this.children ? this.children[0] : null;
    },

    /**
         * 获取当前节点下的最后一个子节点
         * @method lastChild
         * @return { UE.uNode } 返回最后一个子节点
         * @example
         * ```javascript
         * node.lastChild(); //返回最后一个子节点
         * ```
         */
    lastChild: function() {
      //            if (this.type != 'element' || dtd.$empty[this.tagName] ) {
      //                return this;
      //            }
      return this.children ? this.children[this.children.length - 1] : null;
    },

    /**
         * 获取和当前节点有相同父亲节点的前一个节点
         * @method previousSibling
         * @return { UE.uNode } 返回前一个节点
         * @example
         * ```javascript
         * node.children[2].previousSibling(); //返回子节点node.children[1]
         * ```
         */
    previousSibling: function() {
      var parent = this.parentNode;
      for (var i = 0, ci; (ci = parent.children[i]); i++) {
        if (ci === this) {
          return i == 0 ? null : parent.children[i - 1];
        }
      }
    },

    /**
         * 获取和当前节点有相同父亲节点的后一个节点
         * @method nextSibling
         * @return { UE.uNode } 返回后一个节点,找不到返回null
         * @example
         * ```javascript
         * node.children[2].nextSibling(); //如果有，返回子节点node.children[3]
         * ```
         */
    nextSibling: function() {
      var parent = this.parentNode;
      for (var i = 0, ci; (ci = parent.children[i++]); ) {
        if (ci === this) {
          return parent.children[i];
        }
      }
    },

    /**
         * 用新的节点替换当前节点
         * @method replaceChild
         * @param { UE.uNode } target 要替换成该节点参数
         * @param { UE.uNode } source 要被替换掉的节点
         * @return { UE.uNode } 返回替换之后的节点对象
         * @example
         * ```javascript
         * node.replaceChild(newNode, childNode); //用newNode替换childNode,childNode是node的子节点
         * ```
         */
    replaceChild: function(target, source) {
      if (this.children) {
        if (target.parentNode) {
          target.parentNode.removeChild(target);
        }
        for (var i = 0, ci; (ci = this.children[i]); i++) {
          if (ci === source) {
            this.children.splice(i, 1, target);
            source.parentNode = null;
            target.parentNode = this;
            return target;
          }
        }
      }
    },

    /**
         * 在节点的子节点列表最后位置插入一个节点
         * @method appendChild
         * @param { UE.uNode } node 要插入的节点
         * @return { UE.uNode } 返回刚插入的子节点
         * @example
         * ```javascript
         * node.appendChild( newNode ); //在node内插入子节点newNode
         * ```
         */
    appendChild: function(node) {
      if (
        this.type == "root" ||
        (this.type == "element" && !dtd.$empty[this.tagName])
      ) {
        if (!this.children) {
          this.children = [];
        }
        if (node.parentNode) {
          node.parentNode.removeChild(node);
        }
        for (var i = 0, ci; (ci = this.children[i]); i++) {
          if (ci === node) {
            this.children.splice(i, 1);
            break;
          }
        }
        this.children.push(node);
        node.parentNode = this;
        return node;
      }
    },

    /**
         * 在传入节点的前面插入一个节点
         * @method insertBefore
         * @param { UE.uNode } target 要插入的节点
         * @param { UE.uNode } source 在该参数节点前面插入
         * @return { UE.uNode } 返回刚插入的子节点
         * @example
         * ```javascript
         * node.parentNode.insertBefore(newNode, node); //在node节点后面插入newNode
         * ```
         */
    insertBefore: function(target, source) {
      if (this.children) {
        if (target.parentNode) {
          target.parentNode.removeChild(target);
        }
        for (var i = 0, ci; (ci = this.children[i]); i++) {
          if (ci === source) {
            this.children.splice(i, 0, target);
            target.parentNode = this;
            return target;
          }
        }
      }
    },

    /**
         * 在传入节点的后面插入一个节点
         * @method insertAfter
         * @param { UE.uNode } target 要插入的节点
         * @param { UE.uNode } source 在该参数节点后面插入
         * @return { UE.uNode } 返回刚插入的子节点
         * @example
         * ```javascript
         * node.parentNode.insertAfter(newNode, node); //在node节点后面插入newNode
         * ```
         */
    insertAfter: function(target, source) {
      if (this.children) {
        if (target.parentNode) {
          target.parentNode.removeChild(target);
        }
        for (var i = 0, ci; (ci = this.children[i]); i++) {
          if (ci === source) {
            this.children.splice(i + 1, 0, target);
            target.parentNode = this;
            return target;
          }
        }
      }
    },

    /**
         * 从当前节点的子节点列表中，移除节点
         * @method removeChild
         * @param { UE.uNode } node 要移除的节点引用
         * @param { Boolean } keepChildren 是否保留移除节点的子节点，若传入true，自动把移除节点的子节点插入到移除的位置
         * @return { * } 返回刚移除的子节点
         * @example
         * ```javascript
         * node.removeChild(childNode,true); //在node的子节点列表中移除child节点，并且吧child的子节点插入到移除的位置
         * ```
         */
    removeChild: function(node, keepChildren) {
      if (this.children) {
        for (var i = 0, ci; (ci = this.children[i]); i++) {
          if (ci === node) {
            this.children.splice(i, 1);
            ci.parentNode = null;
            if (keepChildren && ci.children && ci.children.length) {
              for (var j = 0, cj; (cj = ci.children[j]); j++) {
                this.children.splice(i + j, 0, cj);
                cj.parentNode = this;
              }
            }
            return ci;
          }
        }
      }
    },

    /**
         * 获取当前节点所代表的元素属性，即获取attrs对象下的属性值
         * @method getAttr
         * @param { String } attrName 要获取的属性名称
         * @return { * } 返回attrs对象下的属性值
         * @example
         * ```javascript
         * node.getAttr('title');
         * ```
         */
    getAttr: function(attrName) {
      return this.attrs && this.attrs[attrName.toLowerCase()];
    },

    /**
         * 设置当前节点所代表的元素属性，即设置attrs对象下的属性值
         * @method setAttr
         * @param { String } attrName 要设置的属性名称
         * @param { * } attrVal 要设置的属性值，类型视设置的属性而定
         * @return { * } 返回attrs对象下的属性值
         * @example
         * ```javascript
         * node.setAttr('title','标题');
         * ```
         */
    setAttr: function(attrName, attrVal) {
      if (!attrName) {
        delete this.attrs;
        return;
      }
      if (!this.attrs) {
        this.attrs = {};
      }
      if (utils.isObject(attrName)) {
        for (var a in attrName) {
          if (!attrName[a]) {
            delete this.attrs[a];
          } else {
            this.attrs[a.toLowerCase()] = attrName[a];
          }
        }
      } else {
        if (!attrVal) {
          delete this.attrs[attrName];
        } else {
          this.attrs[attrName.toLowerCase()] = attrVal;
        }
      }
    },

    /**
         * 获取当前节点在父节点下的位置索引
         * @method getIndex
         * @return { Number } 返回索引数值，如果没有父节点，返回-1
         * @example
         * ```javascript
         * node.getIndex();
         * ```
         */
    getIndex: function() {
      var parent = this.parentNode;
      for (var i = 0, ci; (ci = parent.children[i]); i++) {
        if (ci === this) {
          return i;
        }
      }
      return -1;
    },

    /**
         * 在当前节点下，根据id查找节点
         * @method getNodeById
         * @param { String } id 要查找的id
         * @return { UE.uNode } 返回找到的节点
         * @example
         * ```javascript
         * node.getNodeById('textId');
         * ```
         */
    getNodeById: function(id) {
      var node;
      if (this.children && this.children.length) {
        for (var i = 0, ci; (ci = this.children[i++]); ) {
          if ((node = getNodeById(ci, id))) {
            return node;
          }
        }
      }
    },

    /**
         * 在当前节点下，根据元素名称查找节点列表
         * @method getNodesByTagName
         * @param { String } tagNames 要查找的元素名称
         * @return { Array } 返回找到的节点列表
         * @example
         * ```javascript
         * node.getNodesByTagName('span');
         * ```
         */
    getNodesByTagName: function(tagNames) {
      tagNames = utils.trim(tagNames).replace(/[ ]{2,}/g, " ").split(" ");
      var arr = [],
        me = this;
      utils.each(tagNames, function(tagName) {
        if (me.children && me.children.length) {
          for (var i = 0, ci; (ci = me.children[i++]); ) {
            getNodesByTagName(ci, tagName, arr);
          }
        }
      });
      return arr;
    },

    /**
         * 根据样式名称，获取节点的样式值
         * @method getStyle
         * @param { String } name 要获取的样式名称
         * @return { String } 返回样式值
         * @example
         * ```javascript
         * node.getStyle('font-size');
         * ```
         */
    getStyle: function(name) {
      var cssStyle = this.getAttr("style");
      if (!cssStyle) {
        return "";
      }
      var reg = new RegExp("(^|;)\\s*" + name + ":([^;]+)", "i");
      var match = cssStyle.match(reg);
      if (match && match[0]) {
        return match[2];
      }
      return "";
    },

    /**
         * 给节点设置样式
         * @method setStyle
         * @param { String } name 要设置的的样式名称
         * @param { String } val 要设置的的样值
         * @example
         * ```javascript
         * node.setStyle('font-size', '12px');
         * ```
         */
    setStyle: function(name, val) {
      function exec(name, val) {
        var reg = new RegExp("(^|;)\\s*" + name + ":([^;]+;?)", "gi");
        cssStyle = cssStyle.replace(reg, "$1");
        if (val) {
          cssStyle = name + ":" + utils.unhtml(val) + ";" + cssStyle;
        }
      }

      var cssStyle = this.getAttr("style");
      if (!cssStyle) {
        cssStyle = "";
      }
      if (utils.isObject(name)) {
        for (var a in name) {
          exec(a, name[a]);
        }
      } else {
        exec(name, val);
      }
      this.setAttr("style", utils.trim(cssStyle));
    },

    /**
         * 传入一个函数，递归遍历当前节点下的所有节点
         * @method traversal
         * @param { Function } fn 遍历到节点的时，传入节点作为参数，运行此函数
         * @example
         * ```javascript
         * traversal(node, function(){
         *     console.log(node.type);
         * });
         * ```
         */
    traversal: function(fn) {
      if (this.children && this.children.length) {
        nodeTraversal(this, fn);
      }
      return this;
    }
  };
})();


// core/htmlparser.js
/**
 * html字符串转换成uNode节点
 * @file
 * @module UE
 * @since 1.2.6.1
 */

/**
 * UEditor公用空间，UEditor所有的功能都挂载在该空间下
 * @unfile
 * @module UE
 */

/**
 * html字符串转换成uNode节点的静态方法
 * @method htmlparser
 * @param { String } htmlstr 要转换的html代码
 * @param { Boolean } ignoreBlank 若设置为true，转换的时候忽略\n\r\t等空白字符
 * @return { uNode } 给定的html片段转换形成的uNode对象
 * @example
 * ```javascript
 * var root = UE.htmlparser('<p><b>htmlparser</b></p>', true);
 * ```
 */

var htmlparser = (UE.htmlparser = function(htmlstr, ignoreBlank) {
  //todo 原来的方式  [^"'<>\/] 有\/就不能配对上 <TD vAlign=top background=../AAA.JPG> 这样的标签了
  //先去掉了，加上的原因忘了，这里先记录
  //var re_tag = /<(?:(?:\/([^>]+)>)|(?:!--([\S|\s]*?)-->)|(?:([^\s\/<>]+)\s*((?:(?:"[^"]*")|(?:'[^']*')|[^"'<>])*)\/?>))/g,
  //以上的正则表达式无法匹配:<div style="text-align:center;font-family:" font-size:14px;"=""><img src="http://hs-album.oss.aliyuncs.com/static/27/78/35/image/20161206/20161206174331_41105.gif" alt="" /><br /></div>
  //修改为如下正则表达式:
  var re_tag = /<(?:(?:\/([^>]+)>)|(?:!--([\S|\s]*?)-->)|(?:([^\/\s>]+)((?:\s+[\w\-:.]+(?:\s*=\s*?(?:(?:"[^"]*")|(?:'[^']*')|[^\s"'\/>]+))?)*)[\S\s]*?(\/?)>))/g,
    re_attr = /([\w\-:.]+)(?:(?:\s*=\s*(?:(?:"([^"]*)")|(?:'([^']*)')|([^\s>]+)))|(?=\s|$))/g;

  //ie下取得的html可能会有\n存在，要去掉，在处理replace(/[\t\r\n]*/g,'');代码高量的\n不能去除
  var allowEmptyTags = {
    b: 1,
    code: 1,
    i: 1,
    u: 1,
    strike: 1,
    s: 1,
    tt: 1,
    strong: 1,
    q: 1,
    samp: 1,
    em: 1,
    span: 1,
    sub: 1,
    img: 1,
    sup: 1,
    font: 1,
    big: 1,
    small: 1,
    iframe: 1,
    a: 1,
    br: 1,
    pre: 1
  };
  htmlstr = htmlstr.replace(new RegExp(domUtils.fillChar, "g"), "");
  if (!ignoreBlank) {
    htmlstr = htmlstr.replace(
      new RegExp(
        "[\\r\\t\\n" +
          (ignoreBlank ? "" : " ") +
          "]*</?(\\w+)\\s*(?:[^>]*)>[\\r\\t\\n" +
          (ignoreBlank ? "" : " ") +
          "]*",
        "g"
      ),
      function(a, b) {
        //br暂时单独处理
        if (b && allowEmptyTags[b.toLowerCase()]) {
          return a.replace(/(^[\n\r]+)|([\n\r]+$)/g, "");
        }
        return a
          .replace(new RegExp("^[\\r\\n" + (ignoreBlank ? "" : " ") + "]+"), "")
          .replace(
            new RegExp("[\\r\\n" + (ignoreBlank ? "" : " ") + "]+$"),
            ""
          );
      }
    );
  }

  var notTransAttrs = {
    href: 1,
    src: 1
  };

  var uNode = UE.uNode,
    needParentNode = {
      td: "tr",
      tr: ["tbody", "thead", "tfoot"],
      tbody: "table",
      th: "tr",
      thead: "table",
      tfoot: "table",
      caption: "table",
      li: ["ul", "ol"],
      dt: "dl",
      dd: "dl",
      option: "select"
    },
    needChild = {
      ol: "li",
      ul: "li"
    };

  function text(parent, data) {
    if (needChild[parent.tagName]) {
      var tmpNode = uNode.createElement(needChild[parent.tagName]);
      parent.appendChild(tmpNode);
      tmpNode.appendChild(uNode.createText(data));
      parent = tmpNode;
    } else {
      parent.appendChild(uNode.createText(data));
    }
  }

  function element(parent, tagName, htmlattr) {
    var needParentTag;
    if ((needParentTag = needParentNode[tagName])) {
      var tmpParent = parent,
        hasParent;
      while (tmpParent.type != "root") {
        if (
          utils.isArray(needParentTag)
            ? utils.indexOf(needParentTag, tmpParent.tagName) != -1
            : needParentTag == tmpParent.tagName
        ) {
          parent = tmpParent;
          hasParent = true;
          break;
        }
        tmpParent = tmpParent.parentNode;
      }
      if (!hasParent) {
        parent = element(
          parent,
          utils.isArray(needParentTag) ? needParentTag[0] : needParentTag
        );
      }
    }
    //按dtd处理嵌套
    //        if(parent.type != 'root' && !dtd[parent.tagName][tagName])
    //            parent = parent.parentNode;
    var elm = new uNode({
      parentNode: parent,
      type: "element",
      tagName: tagName.toLowerCase(),
      //是自闭合的处理一下
      children: dtd.$empty[tagName] ? null : []
    });
    //如果属性存在，处理属性
    if (htmlattr) {
      var attrs = {},
        match;
      while ((match = re_attr.exec(htmlattr))) {
        attrs[match[1].toLowerCase()] = notTransAttrs[match[1].toLowerCase()]
          ? match[2] || match[3] || match[4]
          : utils.unhtml(match[2] || match[3] || match[4]);
      }
      elm.attrs = attrs;
    }
    //trace:3970
    //        //如果parent下不能放elm
    //        if(dtd.$inline[parent.tagName] && dtd.$block[elm.tagName] && !dtd[parent.tagName][elm.tagName]){
    //            parent = parent.parentNode;
    //            elm.parentNode = parent;
    //        }
    parent.children.push(elm);
    //如果是自闭合节点返回父亲节点
    return dtd.$empty[tagName] ? parent : elm;
  }

  function comment(parent, data) {
    parent.children.push(
      new uNode({
        type: "comment",
        data: data,
        parentNode: parent
      })
    );
  }

  var match,
    currentIndex = 0,
    nextIndex = 0;
  //设置根节点
  var root = new uNode({
    type: "root",
    children: []
  });
  var currentParent = root;

  while ((match = re_tag.exec(htmlstr))) {
    currentIndex = match.index;
    try {
      if (currentIndex > nextIndex) {
        //text node
        text(currentParent, htmlstr.slice(nextIndex, currentIndex));
      }
      if (match[3]) {
        if (dtd.$cdata[currentParent.tagName]) {
          text(currentParent, match[0]);
        } else {
          //start tag
          currentParent = element(
            currentParent,
            match[3].toLowerCase(),
            match[4]
          );
        }
      } else if (match[1]) {
        if (currentParent.type != "root") {
          if (dtd.$cdata[currentParent.tagName] && !dtd.$cdata[match[1]]) {
            text(currentParent, match[0]);
          } else {
            var tmpParent = currentParent;
            while (
              currentParent.type == "element" &&
              currentParent.tagName != match[1].toLowerCase()
            ) {
              currentParent = currentParent.parentNode;
              if (currentParent.type == "root") {
                currentParent = tmpParent;
                throw "break";
              }
            }
            //end tag
            currentParent = currentParent.parentNode;
          }
        }
      } else if (match[2]) {
        //comment
        comment(currentParent, match[2]);
      }
    } catch (e) {}

    nextIndex = re_tag.lastIndex;
  }
  //如果结束是文本，就有可能丢掉，所以这里手动判断一下
  //例如 <li>sdfsdfsdf<li>sdfsdfsdfsdf
  if (nextIndex < htmlstr.length) {
    text(currentParent, htmlstr.slice(nextIndex));
  }
  return root;
});


// core/filternode.js
/**
 * UE过滤节点的静态方法
 * @file
 */

/**
 * UEditor公用空间，UEditor所有的功能都挂载在该空间下
 * @module UE
 */

/**
 * 根据传入节点和过滤规则过滤相应节点
 * @module UE
 * @since 1.2.6.1
 * @method filterNode
 * @param { Object } root 指定root节点
 * @param { Object } rules 过滤规则json对象
 * @example
 * ```javascript
 * UE.filterNode(root,editor.options.filterRules);
 * ```
 */
var filterNode = (UE.filterNode = (function() {
  function filterNode(node, rules) {
    switch (node.type) {
      case "text":
        break;
      case "element":
        var val;
        if ((val = rules[node.tagName])) {
          if (val === "-") {
            node.parentNode.removeChild(node);
          } else if (utils.isFunction(val)) {
            var parentNode = node.parentNode,
              index = node.getIndex();
            val(node);
            if (node.parentNode) {
              if (node.children) {
                for (var i = 0, ci; (ci = node.children[i]); ) {
                  filterNode(ci, rules);
                  if (ci.parentNode) {
                    i++;
                  }
                }
              }
            } else {
              for (var i = index, ci; (ci = parentNode.children[i]); ) {
                filterNode(ci, rules);
                if (ci.parentNode) {
                  i++;
                }
              }
            }
          } else {
            var attrs = val["$"];
            if (attrs && node.attrs) {
              var tmpAttrs = {},
                tmpVal;
              for (var a in attrs) {
                tmpVal = node.getAttr(a);
                //todo 只先对style单独处理
                if (a == "style" && utils.isArray(attrs[a])) {
                  var tmpCssStyle = [];
                  utils.each(attrs[a], function(v) {
                    var tmp;
                    if ((tmp = node.getStyle(v))) {
                      tmpCssStyle.push(v + ":" + tmp);
                    }
                  });
                  tmpVal = tmpCssStyle.join(";");
                }
                if (tmpVal) {
                  tmpAttrs[a] = tmpVal;
                }
              }
              node.attrs = tmpAttrs;
            }
            if (node.children) {
              for (var i = 0, ci; (ci = node.children[i]); ) {
                filterNode(ci, rules);
                if (ci.parentNode) {
                  i++;
                }
              }
            }
          }
        } else {
          //如果不在名单里扣出子节点并删除该节点,cdata除外
          if (dtd.$cdata[node.tagName]) {
            node.parentNode.removeChild(node);
          } else {
            var parentNode = node.parentNode,
              index = node.getIndex();
            node.parentNode.removeChild(node, true);
            for (var i = index, ci; (ci = parentNode.children[i]); ) {
              filterNode(ci, rules);
              if (ci.parentNode) {
                i++;
              }
            }
          }
        }
        break;
      case "comment":
        node.parentNode.removeChild(node);
    }
  }
  return function(root, rules) {
    if (utils.isEmptyObject(rules)) {
      return root;
    }
    var val;
    if ((val = rules["-"])) {
      utils.each(val.split(" "), function(k) {
        rules[k] = "-";
      });
    }
    for (var i = 0, ci; (ci = root.children[i]); ) {
      filterNode(ci, rules);
      if (ci.parentNode) {
        i++;
      }
    }
    return root;
  };
})());


// core/plugin.js
/**
 * Created with JetBrains PhpStorm.
 * User: campaign
 * Date: 10/8/13
 * Time: 6:15 PM
 * To change this template use File | Settings | File Templates.
 */
UE.plugin = (function() {
  var _plugins = {};
  return {
    register: function(pluginName, fn, oldOptionName, afterDisabled) {
      if (oldOptionName && utils.isFunction(oldOptionName)) {
        afterDisabled = oldOptionName;
        oldOptionName = null;
      }
      _plugins[pluginName] = {
        optionName: oldOptionName || pluginName,
        execFn: fn,
        //当插件被禁用时执行
        afterDisabled: afterDisabled
      };
    },
    load: function(editor) {
      utils.each(_plugins, function(plugin) {
        var _export = plugin.execFn.call(editor);
        if (editor.options[plugin.optionName] !== false) {
          if (_export) {
            //后边需要再做扩展
            utils.each(_export, function(v, k) {
              switch (k.toLowerCase()) {
                case "shortcutkey":
                  editor.addshortcutkey(v);
                  break;
                case "bindevents":
                  utils.each(v, function(fn, eventName) {
                    editor.addListener(eventName, fn);
                  });
                  break;
                case "bindmultievents":
                  utils.each(utils.isArray(v) ? v : [v], function(event) {
                    var types = utils.trim(event.type).split(/\s+/);
                    utils.each(types, function(eventName) {
                      editor.addListener(eventName, event.handler);
                    });
                  });
                  break;
                case "commands":
                  utils.each(v, function(execFn, execName) {
                    editor.commands[execName] = execFn;
                  });
                  break;
                case "outputrule":
                  editor.addOutputRule(v);
                  break;
                case "inputrule":
                  editor.addInputRule(v);
                  break;
                case "defaultoptions":
                  editor.setOpt(v);
              }
            });
          }
        } else if (plugin.afterDisabled) {
          plugin.afterDisabled.call(editor);
        }
      });
      //向下兼容
      utils.each(UE.plugins, function(plugin) {
        plugin.call(editor);
      });
    },
    run: function(pluginName, editor) {
      var plugin = _plugins[pluginName];
      if (plugin) {
        plugin.exeFn.call(editor);
      }
    }
  };
})();


// core/keymap.js
var keymap = (UE.keymap = {
  Backspace: 8,
  Tab: 9,
  Enter: 13,

  Shift: 16,
  Control: 17,
  Alt: 18,
  CapsLock: 20,

  Esc: 27,

  Spacebar: 32,

  PageUp: 33,
  PageDown: 34,
  End: 35,
  Home: 36,

  Left: 37,
  Up: 38,
  Right: 39,
  Down: 40,

  Insert: 45,

  Del: 46,

  NumLock: 144,

  Cmd: 91,

  "=": 187,
  "-": 189,

  b: 66,
  i: 73,
  //回退
  z: 90,
  y: 89,
  //粘贴
  v: 86,
  x: 88,

  s: 83,

  n: 78
});


// core/localstorage.js
var LocalStorage = (UE.LocalStorage = (function () {

    var storage = window.localStorage

    return {
        saveLocalData: function (key, data) {
            // console.log('saveLocalData', key, data);
            if (!storage) {
                return false;
            }
            storage.setItem(key, data);
            return true;
        },
        getLocalData: function (key) {
            // console.log('getLocalData', key);
            if (!storage) {
                return null;
            }
            return storage.getItem(key) || null;
        },
        removeItem: function (key) {
            // console.log('removeItem', key);
            storage && storage.removeItem(key);
        }
    };

})());

(function () {

    var ROOT_KEY = "UEditorPlusPref";

    UE.Editor.prototype.setPreferences = function (key, value) {
        // console.log('setPreferences', key, value);
        var obj = {};
        if (utils.isString(key)) {
            obj[key] = value;
        } else {
            obj = key;
        }
        var data = LocalStorage.getLocalData(ROOT_KEY);
        if (data && (data = utils.str2json(data))) {
            utils.extend(data, obj);
        } else {
            data = obj;
        }
        data && LocalStorage.saveLocalData(ROOT_KEY, utils.json2str(data));
    };

    UE.Editor.prototype.getPreferences = function (key) {
        // console.log('getPreferences', key);
        var data = LocalStorage.getLocalData(ROOT_KEY);
        if (data && (data = utils.str2json(data))) {
            return key ? data[key] : data;
        }
        return null;
    };

    UE.Editor.prototype.removePreferences = function (key) {
        // console.log('removePreferences', key);
        var data = LocalStorage.getLocalData(ROOT_KEY);
        if (data && (data = utils.str2json(data))) {
            data[key] = undefined;
            delete data[key];
        }
        data && LocalStorage.saveLocalData(ROOT_KEY, utils.json2str(data));
    };
})();


// plugins/defaultfilter.js
///import core
///plugin 编辑器默认的过滤转换机制

UE.plugins["defaultfilter"] = function() {
  var me = this;
  me.setOpt({
    allowDivTransToP: true,
    disabledTableInTable: true,
    rgb2Hex: true
  });
  //默认的过滤处理
  //进入编辑器的内容处理
  me.addInputRule(function(root) {
    var allowDivTransToP = this.options.allowDivTransToP;
    var val;
    function tdParent(node) {
      while (node && node.type == "element") {
        if (node.tagName == "td") {
          return true;
        }
        node = node.parentNode;
      }
      return false;
    }
    //进行默认的处理
    root.traversal(function(node) {
      if (node.type == "element") {
        if (
          !dtd.$cdata[node.tagName] &&
          me.options.autoClearEmptyNode &&
          dtd.$inline[node.tagName] &&
          !dtd.$empty[node.tagName] &&
          (!node.attrs || utils.isEmptyObject(node.attrs))
        ) {
          if (!node.firstChild()) node.parentNode.removeChild(node);
          else if (
            node.tagName == "span" &&
            (!node.attrs || utils.isEmptyObject(node.attrs))
          ) {
            node.parentNode.removeChild(node, true);
          }
          return;
        }
        switch (node.tagName) {
          case "style":
          case "script":
            node.setAttr({
              cdata_tag: node.tagName,
              cdata_data: node.innerHTML() || "",
              _ue_custom_node_: "true"
            });
            node.tagName = "div";
            node.innerHTML("");
            break;
          case "a":
            if ((val = node.getAttr("href"))) {
              node.setAttr("_href", val);
            }
            break;
          case "img":
            //todo base64暂时去掉，后边做远程图片上传后，干掉这个
            if ((val = node.getAttr("src"))) {
              if (/^data:/.test(val)) {
                node.parentNode.removeChild(node);
                break;
              }
            }
            node.setAttr("_src", node.getAttr("src"));
            break;
          case "span":
            if (browser.webkit && (val = node.getStyle("white-space"))) {
              if (/nowrap|normal/.test(val)) {
                node.setStyle("white-space", "");
                if (
                  me.options.autoClearEmptyNode &&
                  utils.isEmptyObject(node.attrs)
                ) {
                  node.parentNode.removeChild(node, true);
                }
              }
            }
            val = node.getAttr("id");
            if (val && /^_baidu_bookmark_/i.test(val)) {
              node.parentNode.removeChild(node);
            }
            break;
          case "p":
            if ((val = node.getAttr("align"))) {
              node.setAttr("align");
              node.setStyle("text-align", val);
            }
            //trace:3431
            //                        var cssStyle = node.getAttr('style');
            //                        if (cssStyle) {
            //                            cssStyle = cssStyle.replace(/(margin|padding)[^;]+/g, '');
            //                            node.setAttr('style', cssStyle)
            //
            //                        }
            //p标签不允许嵌套
            utils.each(node.children, function(n) {
              if (n.type == "element" && n.tagName == "p") {
                var next = n.nextSibling();
                node.parentNode.insertAfter(n, node);
                var last = n;
                while (next) {
                  var tmp = next.nextSibling();
                  node.parentNode.insertAfter(next, last);
                  last = next;
                  next = tmp;
                }
                return false;
              }
            });
            if (!node.firstChild()) {
              node.innerHTML(browser.ie ? "&nbsp;" : "<br/>");
            }
            break;
          case "div":
            if (node.getAttr("cdata_tag")) {
              break;
            }
            //针对代码这里不处理插入代码的div
            val = node.getAttr("class");
            if (val && /^line number\d+/.test(val)) {
              break;
            }
            if (!allowDivTransToP) {
              break;
            }
            var tmpNode,
              p = UE.uNode.createElement("p");
            while ((tmpNode = node.firstChild())) {
              if (
                tmpNode.type == "text" ||
                !UE.dom.dtd.$block[tmpNode.tagName]
              ) {
                p.appendChild(tmpNode);
              } else {
                if (p.firstChild()) {
                  node.parentNode.insertBefore(p, node);
                  p = UE.uNode.createElement("p");
                } else {
                  node.parentNode.insertBefore(tmpNode, node);
                }
              }
            }
            if (p.firstChild()) {
              node.parentNode.insertBefore(p, node);
            }
            node.parentNode.removeChild(node);
            break;
          case "dl":
            node.tagName = "ul";
            break;
          case "dt":
          case "dd":
            node.tagName = "li";
            break;
          case "li":
            var className = node.getAttr("class");
            if (!className || !/list\-/.test(className)) {
              node.setAttr();
            }
            var tmpNodes = node.getNodesByTagName("ol ul");
            UE.utils.each(tmpNodes, function(n) {
              node.parentNode.insertAfter(n, node);
            });
            break;
          case "td":
          case "th":
          case "caption":
            if (!node.children || !node.children.length) {
              node.appendChild(
                browser.ie11below
                  ? UE.uNode.createText(" ")
                  : UE.uNode.createElement("br")
              );
            }
            break;
          case "table":
            if (me.options.disabledTableInTable && tdParent(node)) {
              node.parentNode.insertBefore(
                UE.uNode.createText(node.innerText()),
                node
              );
              node.parentNode.removeChild(node);
            }
        }
      }
      //            if(node.type == 'comment'){
      //                node.parentNode.removeChild(node);
      //            }
    });
  });

  //从编辑器出去的内容处理
  me.addOutputRule(function(root) {
    var val;
    root.traversal(function(node) {
      if (node.type == "element") {
        if (
          me.options.autoClearEmptyNode &&
          dtd.$inline[node.tagName] &&
          !dtd.$empty[node.tagName] &&
          (!node.attrs || utils.isEmptyObject(node.attrs))
        ) {
          if (!node.firstChild()) node.parentNode.removeChild(node);
          else if (
            node.tagName == "span" &&
            (!node.attrs || utils.isEmptyObject(node.attrs))
          ) {
            node.parentNode.removeChild(node, true);
          }
          return;
        }
        switch (node.tagName) {
          case "div":
            if ((val = node.getAttr("cdata_tag"))) {
              node.tagName = val;
              node.appendChild(UE.uNode.createText(node.getAttr("cdata_data")));
              node.setAttr({
                cdata_tag: "",
                cdata_data: "",
                _ue_custom_node_: ""
              });
            }
            break;
          case "a":
            if ((val = node.getAttr("_href"))) {
              node.setAttr({
                href: utils.html(val),
                _href: ""
              });
            }
            break;
            break;
          case "span":
            val = node.getAttr("id");
            if (val && /^_baidu_bookmark_/i.test(val)) {
              node.parentNode.removeChild(node);
            }
            //将color的rgb格式转换为#16进制格式
            if (me.getOpt("rgb2Hex")) {
              var cssStyle = node.getAttr("style");
              if (cssStyle) {
                node.setAttr(
                  "style",
                  cssStyle.replace(/rgba?\(([\d,\s]+)\)/g, function(a, value) {
                    var array = value.split(",");
                    if (array.length > 3) return "";
                    value = "#";
                    for (var i = 0, color; (color = array[i++]); ) {
                      color = parseInt(
                        color.replace(/[^\d]/gi, ""),
                        10
                      ).toString(16);
                      value += color.length == 1 ? "0" + color : color;
                    }
                    return value.toUpperCase();
                  })
                );
              }
            }
            break;
          case "img":
            if ((val = node.getAttr("_src"))) {
              node.setAttr({
                src: node.getAttr("_src"),
                _src: ""
              });
            }
        }
      }
    });
  });
};


// plugins/inserthtml.js
/**
 * 插入html字符串插件
 * @file
 * @since 1.2.6.1
 */

/**
 * 插入html代码
 * @command inserthtml
 * @method execCommand
 * @param { String } cmd 命令字符串
 * @param { String } html 插入的html字符串
 * @remaind 插入的标签内容是在当前的选区位置上插入，如果当前是闭合状态，那直接插入内容， 如果当前是选中状态，将先清除当前选中内容后，再做插入
 * @warning 注意:该命令会对当前选区的位置，对插入的内容进行过滤转换处理。 过滤的规则遵循html语意化的原则。
 * @example
 * ```javascript
 * //xxx[BB]xxx 当前选区为非闭合选区，选中BB这两个文本
 * //执行命令，插入<b>CC</b>
 * //插入后的效果 xxx<b>CC</b>xxx
 * //<p>xx|xxx</p> 当前选区为闭合状态
 * //插入<p>CC</p>
 * //结果 <p>xx</p><p>CC</p><p>xxx</p>
 * //<p>xxxx</p>|</p>xxx</p> 当前选区在两个p标签之间
 * //插入 xxxx
 * //结果 <p>xxxx</p><p>xxxx</p></p>xxx</p>
 * ```
 */

UE.commands["inserthtml"] = {
  execCommand: function(command, html, notNeedFilter) {
    var me = this,
      range,
      div;
    if (!html) {
      return;
    }
    if (me.fireEvent("beforeinserthtml", html) === true) {
      return;
    }
    range = me.selection.getRange();
    div = range.document.createElement("div");
    div.style.display = "inline";

    if (!notNeedFilter) {
      var root = UE.htmlparser(html);
      //如果给了过滤规则就先进行过滤
      if (me.options.filterRules) {
        UE.filterNode(root, me.options.filterRules);
      }
      //执行默认的处理
      me.filterInputRule(root);
      html = root.toHtml();
    }
    div.innerHTML = utils.trim(html);

    if (!range.collapsed) {
      var tmpNode = range.startContainer;
      if (domUtils.isFillChar(tmpNode)) {
        range.setStartBefore(tmpNode);
      }
      tmpNode = range.endContainer;
      if (domUtils.isFillChar(tmpNode)) {
        range.setEndAfter(tmpNode);
      }
      range.txtToElmBoundary();
      //结束边界可能放到了br的前边，要把br包含进来
      // x[xxx]<br/>
      if (range.endContainer && range.endContainer.nodeType == 1) {
        tmpNode = range.endContainer.childNodes[range.endOffset];
        if (tmpNode && domUtils.isBr(tmpNode)) {
          range.setEndAfter(tmpNode);
        }
      }
      if (range.startOffset == 0) {
        tmpNode = range.startContainer;
        if (domUtils.isBoundaryNode(tmpNode, "firstChild")) {
          tmpNode = range.endContainer;
          if (
            range.endOffset ==
              (tmpNode.nodeType == 3
                ? tmpNode.nodeValue.length
                : tmpNode.childNodes.length) &&
            domUtils.isBoundaryNode(tmpNode, "lastChild")
          ) {
            me.body.innerHTML = "<p>" + (browser.ie ? "" : "<br/>") + "</p>";
            range.setStart(me.body.firstChild, 0).collapse(true);
          }
        }
      }
      !range.collapsed && range.deleteContents();
      if (range.startContainer.nodeType == 1) {
        var child = range.startContainer.childNodes[range.startOffset],
          pre;
        if (
          child &&
          domUtils.isBlockElm(child) &&
          (pre = child.previousSibling) &&
          domUtils.isBlockElm(pre)
        ) {
          range.setEnd(pre, pre.childNodes.length).collapse();
          while (child.firstChild) {
            pre.appendChild(child.firstChild);
          }
          domUtils.remove(child);
        }
      }
    }

    var child,
      parent,
      pre,
      tmp,
      hadBreak = 0,
      nextNode;
    //如果当前位置选中了fillchar要干掉，要不会产生空行
    if (range.inFillChar()) {
      child = range.startContainer;
      if (domUtils.isFillChar(child)) {
        range.setStartBefore(child).collapse(true);
        domUtils.remove(child);
      } else if (domUtils.isFillChar(child, true)) {
        child.nodeValue = child.nodeValue.replace(fillCharReg, "");
        range.startOffset--;
        range.collapsed && range.collapse(true);
      }
    }
    //列表单独处理
    var li = domUtils.findParentByTagName(range.startContainer, "li", true);
    if (li) {
      var next, last;
      while ((child = div.firstChild)) {
        //针对hr单独处理一下先
        while (
          child &&
          (child.nodeType == 3 ||
            !domUtils.isBlockElm(child) ||
            child.tagName == "HR")
        ) {
          next = child.nextSibling;
          range.insertNode(child).collapse();
          last = child;
          child = next;
        }
        if (child) {
          if (/^(ol|ul)$/i.test(child.tagName)) {
            while (child.firstChild) {
              last = child.firstChild;
              domUtils.insertAfter(li, child.firstChild);
              li = li.nextSibling;
            }
            domUtils.remove(child);
          } else {
            var tmpLi;
            next = child.nextSibling;
            tmpLi = me.document.createElement("li");
            domUtils.insertAfter(li, tmpLi);
            tmpLi.appendChild(child);
            last = child;
            child = next;
            li = tmpLi;
          }
        }
      }
      li = domUtils.findParentByTagName(range.startContainer, "li", true);
      if (domUtils.isEmptyBlock(li)) {
        domUtils.remove(li);
      }
      if (last) {
        range.setStartAfter(last).collapse(true).select(true);
      }
    } else {
      while ((child = div.firstChild)) {
        if (hadBreak) {
          var p = me.document.createElement("p");
          while (child && (child.nodeType == 3 || !dtd.$block[child.tagName])) {
            nextNode = child.nextSibling;
            p.appendChild(child);
            child = nextNode;
          }
          if (p.firstChild) {
            child = p;
          }
        }
        range.insertNode(child);
        nextNode = child.nextSibling;
        if (
          !hadBreak &&
          child.nodeType == domUtils.NODE_ELEMENT &&
          domUtils.isBlockElm(child)
        ) {
          parent = domUtils.findParent(child, function(node) {
            return domUtils.isBlockElm(node);
          });
          if (
            parent &&
            parent.tagName.toLowerCase() != "body" &&
            !(
              dtd[parent.tagName][child.nodeName] && child.parentNode === parent
            )
          ) {
            if (!dtd[parent.tagName][child.nodeName]) {
              pre = parent;
            } else {
              tmp = child.parentNode;
              while (tmp !== parent) {
                pre = tmp;
                tmp = tmp.parentNode;
              }
            }

            domUtils.breakParent(child, pre || tmp);
            //去掉break后前一个多余的节点  <p>|<[p> ==> <p></p><div></div><p>|</p>
            var pre = child.previousSibling;
            domUtils.trimWhiteTextNode(pre);
            if (!pre.childNodes.length) {
              domUtils.remove(pre);
            }
            //trace:2012,在非ie的情况，切开后剩下的节点有可能不能点入光标添加br占位

            if (
              !browser.ie &&
              (next = child.nextSibling) &&
              domUtils.isBlockElm(next) &&
              next.lastChild &&
              !domUtils.isBr(next.lastChild)
            ) {
              next.appendChild(me.document.createElement("br"));
            }
            hadBreak = 1;
          }
        }
        var next = child.nextSibling;
        if (!div.firstChild && next && domUtils.isBlockElm(next)) {
          range.setStart(next, 0).collapse(true);
          break;
        }
        range.setEndAfter(child).collapse();
      }

      child = range.startContainer;

      if (nextNode && domUtils.isBr(nextNode)) {
        domUtils.remove(nextNode);
      }
      //用chrome可能有空白展位符
      if (domUtils.isBlockElm(child) && domUtils.isEmptyNode(child)) {
        if ((nextNode = child.nextSibling)) {
          domUtils.remove(child);
          if (nextNode.nodeType == 1 && dtd.$block[nextNode.tagName]) {
            range.setStart(nextNode, 0).collapse(true).shrinkBoundary();
          }
        } else {
          try {
            child.innerHTML = browser.ie ? domUtils.fillChar : "<br/>";
          } catch (e) {
            range.setStartBefore(child);
            domUtils.remove(child);
          }
        }
      }
      //加上true因为在删除表情等时会删两次，第一次是删的fillData
      try {
        range.select(true);
      } catch (e) {}
    }

    setTimeout(function() {
      range = me.selection.getRange();
      range.scrollToView(
        me.autoHeightEnabled,
        me.autoHeightEnabled ? domUtils.getXY(me.iframe).y : 0
      );
      me.fireEvent("afterinserthtml", html);
    }, 200);
  }
};


// plugins/autotypeset.js
/**
 * 自动排版
 * @file
 * @since 1.2.6.1
 */

/**
 * 对当前编辑器的内容执行自动排版， 排版的行为根据config配置文件里的“autotypeset”选项进行控制。
 * @command autotypeset
 * @method execCommand
 * @param { String } cmd 命令字符串
 * @example
 * ```javascript
 * editor.execCommand( 'autotypeset' );
 * ```
 */

UE.plugins["autotypeset"] = function() {
  this.setOpt({
    // 自动排版参数
    autotypeset: {
      // 合并空行
      mergeEmptyline: true,
      // 去掉冗余的class
      removeClass: true,
      // 去掉空行
      removeEmptyline: false,
      // 段落的排版方式，可以是 left,right,center,justify 去掉这个属性表示不执行排版
      textAlign: "left",
      // 图片的浮动方式，独占一行剧中,左右浮动，默认: center,left,right,none 去掉这个属性表示不执行排版
      imageBlockLine: "center",
      // 根据规则过滤没事粘贴进来的内容
      pasteFilter: false,
      // 去掉所有的内嵌字号，使用编辑器默认的字号
      clearFontSize: false,
      // 去掉所有的内嵌字体，使用编辑器默认的字体
      clearFontFamily: false,
      // 去掉空节点
      removeEmptyNode: false,
      // 可以去掉的标签
      removeTagNames: utils.extend({ div: 1 }, dtd.$removeEmpty),
      // 行首缩进
      indent: false,
      // 行首缩进的大小
      indentValue: "2em",
      // 全角转半角
      bdc2sb: false,
      // 半角转全角
      tobdc: false
    }
  });

  var me = this,
    opt = me.options.autotypeset,
    remainClass = {
      selectTdClass: 1,
      pagebreak: 1,
      anchorclass: 1
    },
    remainTag = {
      li: 1
    },
    tags = {
      div: 1,
      p: 1,
      //trace:2183 这些也认为是行
      blockquote: 1,
      center: 1,
      h1: 1,
      h2: 1,
      h3: 1,
      h4: 1,
      h5: 1,
      h6: 1,
      span: 1
    },
    highlightCont;
  //升级了版本，但配置项目里没有autotypeset
  if (!opt) {
    return;
  }

  readLocalOpts();

  function isLine(node, notEmpty) {
    if (!node || node.nodeType == 3) return 0;
    if (domUtils.isBr(node)) return 1;
    if (node && node.parentNode && tags[node.tagName.toLowerCase()]) {
      if (
        (highlightCont && highlightCont.contains(node)) ||
        node.getAttribute("pagebreak")
      ) {
        return 0;
      }

      return notEmpty
        ? !domUtils.isEmptyBlock(node)
        : domUtils.isEmptyBlock(
            node,
            new RegExp("[\\s" + domUtils.fillChar + "]", "g")
          );
    }
  }

  function removeNotAttributeSpan(node) {
    if (!node.style.cssText) {
      domUtils.removeAttributes(node, ["style"]);
      if (
        node.tagName.toLowerCase() == "span" &&
        domUtils.hasNoAttributes(node)
      ) {
        domUtils.remove(node, true);
      }
    }
  }
  function autotype(type, html) {
    var me = this,
      cont;
    if (html) {
      if (!opt.pasteFilter) {
        return;
      }
      cont = me.document.createElement("div");
      cont.innerHTML = html.html;
    } else {
      cont = me.document.body;
    }
    var nodes = domUtils.getElementsByTagName(cont, "*");

    // 行首缩进，段落方向，段间距，段内间距
    for (var i = 0, ci; (ci = nodes[i++]); ) {
      if (me.fireEvent("excludeNodeinautotype", ci) === true) {
        continue;
      }
      //font-size
      if (opt.clearFontSize && ci.style.fontSize) {
        domUtils.removeStyle(ci, "font-size");

        removeNotAttributeSpan(ci);
      }
      //font-family
      if (opt.clearFontFamily && ci.style.fontFamily) {
        domUtils.removeStyle(ci, "font-family");
        removeNotAttributeSpan(ci);
      }

      if (isLine(ci)) {
        //合并空行
        if (opt.mergeEmptyline) {
          var next = ci.nextSibling,
            tmpNode,
            isBr = domUtils.isBr(ci);
          while (isLine(next)) {
            tmpNode = next;
            next = tmpNode.nextSibling;
            if (isBr && (!next || (next && !domUtils.isBr(next)))) {
              break;
            }
            domUtils.remove(tmpNode);
          }
        }
        //去掉空行，保留占位的空行
        if (
          opt.removeEmptyline &&
          domUtils.inDoc(ci, cont) &&
          !remainTag[ci.parentNode.tagName.toLowerCase()]
        ) {
          if (domUtils.isBr(ci)) {
            next = ci.nextSibling;
            if (next && !domUtils.isBr(next)) {
              continue;
            }
          }
          domUtils.remove(ci);
          continue;
        }
      }
      if (isLine(ci, true) && ci.tagName != "SPAN") {
        if (opt.indent) {
          ci.style.textIndent = opt.indentValue;
        }
        if (opt.textAlign) {
          ci.style.textAlign = opt.textAlign;
        }
        // if(opt.lineHeight)
        //     ci.style.lineHeight = opt.lineHeight + 'cm';
      }

      //去掉class,保留的class不去掉
      if (
        opt.removeClass &&
        ci.className &&
        !remainClass[ci.className.toLowerCase()]
      ) {
        if (highlightCont && highlightCont.contains(ci)) {
          continue;
        }
        domUtils.removeAttributes(ci, ["class"]);
      }

      //表情不处理
      if (
        opt.imageBlockLine &&
        ci.tagName.toLowerCase() == "img" &&
        !ci.getAttribute("emotion")
      ) {
        if (html) {
          var img = ci;
          switch (opt.imageBlockLine) {
            case "left":
            case "right":
            case "none":
              var pN = img.parentNode,
                tmpNode,
                pre,
                next;
              while (dtd.$inline[pN.tagName] || pN.tagName == "A") {
                pN = pN.parentNode;
              }
              tmpNode = pN;
              if (
                tmpNode.tagName == "P" &&
                domUtils.getStyle(tmpNode, "text-align") == "center"
              ) {
                if (
                  !domUtils.isBody(tmpNode) &&
                  domUtils.getChildCount(tmpNode, function(node) {
                    return !domUtils.isBr(node) && !domUtils.isWhitespace(node);
                  }) == 1
                ) {
                  pre = tmpNode.previousSibling;
                  next = tmpNode.nextSibling;
                  if (
                    pre &&
                    next &&
                    pre.nodeType == 1 &&
                    next.nodeType == 1 &&
                    pre.tagName == next.tagName &&
                    domUtils.isBlockElm(pre)
                  ) {
                    pre.appendChild(tmpNode.firstChild);
                    while (next.firstChild) {
                      pre.appendChild(next.firstChild);
                    }
                    domUtils.remove(tmpNode);
                    domUtils.remove(next);
                  } else {
                    domUtils.setStyle(tmpNode, "text-align", "");
                  }
                }
              }
              domUtils.setStyle(img, "float", opt.imageBlockLine);
              break;
            case "center":
              if (me.queryCommandValue("imagefloat") != "center") {
                pN = img.parentNode;
                domUtils.setStyle(img, "float", "none");
                tmpNode = img;
                while (
                  pN &&
                  domUtils.getChildCount(pN, function(node) {
                    return !domUtils.isBr(node) && !domUtils.isWhitespace(node);
                  }) == 1 &&
                  (dtd.$inline[pN.tagName] || pN.tagName == "A")
                ) {
                  tmpNode = pN;
                  pN = pN.parentNode;
                }
                var pNode = me.document.createElement("p");
                domUtils.setAttributes(pNode, {
                  style: "text-align:center"
                });
                tmpNode.parentNode.insertBefore(pNode, tmpNode);
                pNode.appendChild(tmpNode);
                domUtils.setStyle(tmpNode, "float", "");
              }
          }
        } else {
          var range = me.selection.getRange();
          range.selectNode(ci).select();
          me.execCommand("imagefloat", opt.imageBlockLine);
        }
      }

      //去掉冗余的标签
      if (opt.removeEmptyNode) {
        if (
          opt.removeTagNames[ci.tagName.toLowerCase()] &&
          domUtils.hasNoAttributes(ci) &&
          domUtils.isEmptyBlock(ci)
        ) {
          domUtils.remove(ci);
        }
      }
    }
    if (opt.tobdc) {
      var root = UE.htmlparser(cont.innerHTML);
      root.traversal(function(node) {
        if (node.type == "text") {
          node.data = ToDBC(node.data);
        }
      });
      cont.innerHTML = root.toHtml();
    }
    if (opt.bdc2sb) {
      var root = UE.htmlparser(cont.innerHTML);
      root.traversal(function(node) {
        if (node.type == "text") {
          node.data = DBC2SB(node.data);
        }
      });
      cont.innerHTML = root.toHtml();
    }
    if (html) {
      html.html = cont.innerHTML;
    }
  }
  if (opt.pasteFilter) {
    me.addListener("beforepaste", autotype);
  }

  function DBC2SB(str) {
    var result = "";
    for (var i = 0; i < str.length; i++) {
      var code = str.charCodeAt(i); //获取当前字符的unicode编码
      if (code >= 65281 && code <= 65373) {
        //在这个unicode编码范围中的是所有的英文字母已经各种字符
        result += String.fromCharCode(str.charCodeAt(i) - 65248); //把全角字符的unicode编码转换为对应半角字符的unicode码
      } else if (code == 12288) {
        //空格
        result += String.fromCharCode(str.charCodeAt(i) - 12288 + 32);
      } else {
        result += str.charAt(i);
      }
    }
    return result;
  }
  function ToDBC(txtstring) {
    txtstring = utils.html(txtstring);
    var tmp = "";
    var mark = ""; /*用于判断,如果是html尖括里的标记,则不进行全角的转换*/
    for (var i = 0; i < txtstring.length; i++) {
      if (txtstring.charCodeAt(i) == 32) {
        tmp = tmp + String.fromCharCode(12288);
      } else if (txtstring.charCodeAt(i) < 127) {
        tmp = tmp + String.fromCharCode(txtstring.charCodeAt(i) + 65248);
      } else {
        tmp += txtstring.charAt(i);
      }
    }
    return tmp;
  }

  function readLocalOpts() {
    var cookieOpt = me.getPreferences("autotypeset");
    utils.extend(me.options.autotypeset, cookieOpt);
  }

  me.commands["autotypeset"] = {
    execCommand: function() {
      me.removeListener("beforepaste", autotype);
      if (opt.pasteFilter) {
        me.addListener("beforepaste", autotype);
      }
      autotype.call(me);
    }
  };
};


// plugins/autosubmit.js
/**
 * 快捷键提交
 * @file
 * @since 1.2.6.1
 */

/**
 * 提交表单
 * @command autosubmit
 * @method execCommand
 * @param { String } cmd 命令字符串
 * @example
 * ```javascript
 * editor.execCommand( 'autosubmit' );
 * ```
 */

UE.plugin.register("autosubmit", function() {
  return {
    shortcutkey: {
      autosubmit: "ctrl+13" //手动提交
    },
    commands: {
      autosubmit: {
        execCommand: function() {
          var me = this,
            form = domUtils.findParentByTagName(me.iframe, "form", false);
          if (form) {
            if (me.fireEvent("beforesubmit") === false) {
              return;
            }
            me.sync();
            form.submit();
          }
        }
      }
    }
  };
});


// plugins/background.js
/**
 * 背景插件，为UEditor提供设置背景功能
 * @file
 * @since 1.2.6.1
 */
UE.plugin.register("background", function() {
  var me = this,
    cssRuleId = "editor_background",
    isSetColored,
    reg = new RegExp("body[\\s]*\\{(.+)\\}", "i");

  function stringToObj(str) {
    var obj = {},
      styles = str.split(";");
    utils.each(styles, function(v) {
      var index = v.indexOf(":"),
        key = utils.trim(v.substr(0, index)).toLowerCase();
      key && (obj[key] = utils.trim(v.substr(index + 1) || ""));
    });
    return obj;
  }

  function setBackground(obj) {
    if (obj) {
      var styles = [];
      for (var name in obj) {
        if (obj.hasOwnProperty(name)) {
          styles.push(name + ":" + obj[name] + "; ");
        }
      }
      utils.cssRule(
        cssRuleId,
        styles.length ? "body{" + styles.join("") + "}" : "",
        me.document
      );
    } else {
      utils.cssRule(cssRuleId, "", me.document);
    }
  }
  //重写editor.hasContent方法

  var orgFn = me.hasContents;
  me.hasContents = function() {
    if (me.queryCommandValue("background")) {
      return true;
    }
    return orgFn.apply(me, arguments);
  };
  return {
    bindEvents: {
      getAllHtml: function(type, headHtml) {
        var body = this.body,
          su = domUtils.getComputedStyle(body, "background-image"),
          url = "";
        if (su.indexOf(me.options.imagePath) > 0) {
          url = su
            .substring(su.indexOf(me.options.imagePath), su.length - 1)
            .replace(/"|\(|\)/gi, "");
        } else {
          url = su != "none" ? su.replace(/url\("?|"?\)/gi, "") : "";
        }
        var html = '<style type="text/css">body{';
        var bgObj = {
          "background-color":
            domUtils.getComputedStyle(body, "background-color") || "#ffffff",
          "background-image": url ? "url(" + url + ")" : "",
          "background-repeat":
            domUtils.getComputedStyle(body, "background-repeat") || "",
          "background-position": browser.ie
            ? domUtils.getComputedStyle(body, "background-position-x") +
                " " +
                domUtils.getComputedStyle(body, "background-position-y")
            : domUtils.getComputedStyle(body, "background-position"),
          height: domUtils.getComputedStyle(body, "height")
        };
        for (var name in bgObj) {
          if (bgObj.hasOwnProperty(name)) {
            html += name + ":" + bgObj[name] + "; ";
          }
        }
        html += "}</style> ";
        headHtml.push(html);
      },
      aftersetcontent: function() {
        if (isSetColored == false) setBackground();
      }
    },
    inputRule: function(root) {
      isSetColored = false;
      utils.each(root.getNodesByTagName("p"), function(p) {
        var styles = p.getAttr("data-background");
        if (styles) {
          isSetColored = true;
          setBackground(stringToObj(styles));
          p.parentNode.removeChild(p);
        }
      });
    },
    outputRule: function(root) {
      var me = this,
        styles = (utils.cssRule(cssRuleId, me.document) || "")
          .replace(/[\n\r]+/g, "")
          .match(reg);
      if (styles) {
        root.appendChild(
          UE.uNode.createElement(
            '<p style="display:none;" data-background="' +
              utils.trim(styles[1].replace(/"/g, "").replace(/[\s]+/g, " ")) +
              '"><br/></p>'
          )
        );
      }
    },
    commands: {
      background: {
        execCommand: function(cmd, obj) {
          setBackground(obj);
        },
        queryCommandValue: function() {
          var me = this,
            styles = (utils.cssRule(cssRuleId, me.document) || "")
              .replace(/[\n\r]+/g, "")
              .match(reg);
          return styles ? stringToObj(styles[1]) : null;
        },
        notNeedUndo: true
      }
    }
  };
});


// plugins/image.js
/**
 * 图片插入、排版插件
 * @file
 * @since 1.2.6.1
 */

/**
 * 图片对齐方式
 * @command imagefloat
 * @method execCommand
 * @remind 值center为独占一行居中
 * @param { String } cmd 命令字符串
 * @param { String } align 对齐方式，可传left、right、none、center
 * @remaind center表示图片独占一行
 * @example
 * ```javascript
 * editor.execCommand( 'imagefloat', 'center' );
 * ```
 */

/**
 * 如果选区所在位置是图片区域
 * @command imagefloat
 * @method queryCommandValue
 * @param { String } cmd 命令字符串
 * @return { String } 返回图片对齐方式
 * @example
 * ```javascript
 * editor.queryCommandValue( 'imagefloat' );
 * ```
 */

UE.commands["imagefloat"] = {
  execCommand: function(cmd, align) {
    var me = this,
      range = me.selection.getRange();
    if (!range.collapsed) {
      var img = range.getClosedNode();
      if (img && img.tagName == "IMG") {
        switch (align) {
          case "left":
          case "right":
          case "none":
            var pN = img.parentNode,
              tmpNode,
              pre,
              next;
            while (dtd.$inline[pN.tagName] || pN.tagName == "A") {
              pN = pN.parentNode;
            }
            tmpNode = pN;
            if (
              tmpNode.tagName == "P" &&
              domUtils.getStyle(tmpNode, "text-align") == "center"
            ) {
              if (
                !domUtils.isBody(tmpNode) &&
                domUtils.getChildCount(tmpNode, function(node) {
                  return !domUtils.isBr(node) && !domUtils.isWhitespace(node);
                }) == 1
              ) {
                pre = tmpNode.previousSibling;
                next = tmpNode.nextSibling;
                if (
                  pre &&
                  next &&
                  pre.nodeType == 1 &&
                  next.nodeType == 1 &&
                  pre.tagName == next.tagName &&
                  domUtils.isBlockElm(pre)
                ) {
                  pre.appendChild(tmpNode.firstChild);
                  while (next.firstChild) {
                    pre.appendChild(next.firstChild);
                  }
                  domUtils.remove(tmpNode);
                  domUtils.remove(next);
                } else {
                  domUtils.setStyle(tmpNode, "text-align", "");
                }
              }

              range.selectNode(img).select();
            }
            domUtils.setStyle(img, "float", align == "none" ? "" : align);
            if (align == "none") {
              domUtils.removeAttributes(img, "align");
            }

            break;
          case "center":
            if (me.queryCommandValue("imagefloat") != "center") {
              pN = img.parentNode;
              domUtils.setStyle(img, "float", "");
              domUtils.removeAttributes(img, "align");
              tmpNode = img;
              while (
                pN &&
                domUtils.getChildCount(pN, function(node) {
                  return !domUtils.isBr(node) && !domUtils.isWhitespace(node);
                }) == 1 &&
                (dtd.$inline[pN.tagName] || pN.tagName == "A")
              ) {
                tmpNode = pN;
                pN = pN.parentNode;
              }
              range.setStartBefore(tmpNode).setCursor(false);
              pN = me.document.createElement("div");
              pN.appendChild(tmpNode);
              domUtils.setStyle(tmpNode, "float", "");

              me.execCommand(
                "insertHtml",
                '<p id="_img_parent_tmp" style="text-align:center">' +
                  pN.innerHTML +
                  "</p>"
              );

              tmpNode = me.document.getElementById("_img_parent_tmp");
              tmpNode.removeAttribute("id");
              tmpNode = tmpNode.firstChild;
              range.selectNode(tmpNode).select();
              //去掉后边多余的元素
              next = tmpNode.parentNode.nextSibling;
              if (next && domUtils.isEmptyNode(next)) {
                domUtils.remove(next);
              }
            }

            break;
        }
      }
    }
  },
  queryCommandValue: function() {
    var range = this.selection.getRange(),
      startNode,
      floatStyle;
    if (range.collapsed) {
      return "none";
    }
    startNode = range.getClosedNode();
    if (startNode && startNode.nodeType == 1 && startNode.tagName == "IMG") {
      floatStyle =
        domUtils.getComputedStyle(startNode, "float") ||
        startNode.getAttribute("align");

      if (floatStyle == "none") {
        floatStyle = domUtils.getComputedStyle(
          startNode.parentNode,
          "text-align"
        ) == "center"
          ? "center"
          : floatStyle;
      }
      return {
        left: 1,
        right: 1,
        center: 1
      }[floatStyle]
        ? floatStyle
        : "none";
    }
    return "none";
  },
  queryCommandState: function() {
    var range = this.selection.getRange(),
      startNode;

    if (range.collapsed) return -1;

    startNode = range.getClosedNode();
    if (startNode && startNode.nodeType == 1 && startNode.tagName == "IMG") {
      return 0;
    }
    return -1;
  }
};

/**
 * 插入图片
 * @command insertimage
 * @method execCommand
 * @param { String } cmd 命令字符串
 * @param { Object } opt 属性键值对，这些属性都将被复制到当前插入图片
 * @remind 该命令第二个参数可接受一个图片配置项对象的数组，可以插入多张图片，
 * 此时数组的每一个元素都是一个Object类型的图片属性集合。
 * @example
 * ```javascript
 * editor.execCommand( 'insertimage', {
 *     src:'a/b/c.jpg',
 *     width:'100',
 *     height:'100'
 * } );
 * ```
 * @example
 * ```javascript
 * editor.execCommand( 'insertimage', [{
 *     src:'a/b/c.jpg',
 *     width:'100',
 *     height:'100'
 * },{
 *     src:'a/b/d.jpg',
 *     width:'100',
 *     height:'100'
 * }] );
 * ```
 */

UE.commands["insertimage"] = {
  execCommand: function(cmd, opt) {
    opt = utils.isArray(opt) ? opt : [opt];
    if (!opt.length) {
      return;
    }
    var me = this,
      range = me.selection.getRange(),
      img = range.getClosedNode();

    if (me.fireEvent("beforeinsertimage", opt) === true) {
      return;
    }

    if (
      img &&
      /img/i.test(img.tagName) &&
      (img.className != "edui-faked-video" ||
        img.className.indexOf("edui-upload-video") != -1) &&
      !img.getAttribute("data-word-image")
    ) {
      var first = opt.shift();
      var floatStyle = first["floatStyle"];
      delete first["floatStyle"];
      ////                img.style.border = (first.border||0) +"px solid #000";
      ////                img.style.margin = (first.margin||0) +"px";
      //                img.style.cssText += ';margin:' + (first.margin||0) +"px;" + 'border:' + (first.border||0) +"px solid #000";
      domUtils.setAttributes(img, first);
      me.execCommand("imagefloat", floatStyle);
      if (opt.length > 0) {
        range.setStartAfter(img).setCursor(false, true);
        me.execCommand("insertimage", opt);
      }
    } else {
      var html = [],
        str = "",
        ci;
      ci = opt[0];
      if (opt.length == 1) {
        str =
          '<img src="' +
          ci.src +
          '" ' +
          (ci._src ? ' _src="' + ci._src + '" ' : "") +
          (ci.width ? 'width="' + ci.width + '" ' : "") +
          (ci.height ? ' height="' + ci.height + '" ' : "") +
          (ci["floatStyle"] == "left" || ci["floatStyle"] == "right"
            ? ' style="float:' + ci["floatStyle"] + ';"'
            : "") +
          (ci.title && ci.title != "" ? ' title="' + ci.title + '"' : "") +
          (ci.border && ci.border != "0" ? ' border="' + ci.border + '"' : "") +
          (ci.alt && ci.alt != "" ? ' alt="' + ci.alt + '"' : "") +
          (ci.hspace && ci.hspace != "0"
            ? ' hspace = "' + ci.hspace + '"'
            : "") +
          (ci.vspace && ci.vspace != "0"
            ? ' vspace = "' + ci.vspace + '"'
            : "") +
          "/>";
        if (ci["floatStyle"] == "center") {
          str = '<p style="text-align: center">' + str + "</p>";
        }
        html.push(str);
      } else {
        for (var i = 0; (ci = opt[i++]); ) {
          str =
            "<p " +
            (ci["floatStyle"] == "center"
              ? 'style="text-align: center" '
              : "") +
            '><img src="' +
            ci.src +
            '" ' +
            (ci.width ? 'width="' + ci.width + '" ' : "") +
            (ci._src ? ' _src="' + ci._src + '" ' : "") +
            (ci.height ? ' height="' + ci.height + '" ' : "") +
            ' style="' +
            (ci["floatStyle"] && ci["floatStyle"] != "center"
              ? "float:" + ci["floatStyle"] + ";"
              : "") +
            (ci.border || "") +
            '" ' +
            (ci.title ? ' title="' + ci.title + '"' : "") +
            " /></p>";
          html.push(str);
        }
      }

      me.execCommand("insertHtml", html.join(""));
    }

    me.fireEvent("afterinsertimage", opt);
  }
};


// plugins/justify.js
/**
 * 段落格式
 * @file
 * @since 1.2.6.1
 */

/**
 * 段落对齐方式
 * @command justify
 * @method execCommand
 * @param { String } cmd 命令字符串
 * @param { String } align 对齐方式：left => 居左，right => 居右，center => 居中，justify => 两端对齐
 * @example
 * ```javascript
 * editor.execCommand( 'justify', 'center' );
 * ```
 */
/**
 * 如果选区所在位置是段落区域，返回当前段落对齐方式
 * @command justify
 * @method queryCommandValue
 * @param { String } cmd 命令字符串
 * @return { String } 返回段落对齐方式
 * @example
 * ```javascript
 * editor.queryCommandValue( 'justify' );
 * ```
 */

UE.plugins["justify"] = function() {
  var me = this,
    block = domUtils.isBlockElm,
    defaultValue = {
      left: 1,
      right: 1,
      center: 1,
      justify: 1
    },
    doJustify = function(range, style) {
      var bookmark = range.createBookmark(),
        filterFn = function(node) {
          return node.nodeType == 1
            ? node.tagName.toLowerCase() != "br" &&
                !domUtils.isBookmarkNode(node)
            : !domUtils.isWhitespace(node);
        };

      range.enlarge(true);
      var bookmark2 = range.createBookmark(),
        current = domUtils.getNextDomNode(bookmark2.start, false, filterFn),
        tmpRange = range.cloneRange(),
        tmpNode;
      while (
        current &&
        !(
          domUtils.getPosition(current, bookmark2.end) &
          domUtils.POSITION_FOLLOWING
        )
      ) {
        if (current.nodeType == 3 || !block(current)) {
          tmpRange.setStartBefore(current);
          while (current && current !== bookmark2.end && !block(current)) {
            tmpNode = current;
            current = domUtils.getNextDomNode(current, false, null, function(
              node
            ) {
              return !block(node);
            });
          }
          tmpRange.setEndAfter(tmpNode);
          var common = tmpRange.getCommonAncestor();
          if (!domUtils.isBody(common) && block(common)) {
            domUtils.setStyles(
              common,
              utils.isString(style) ? { "text-align": style } : style
            );
            current = common;
          } else {
            var p = range.document.createElement("p");
            domUtils.setStyles(
              p,
              utils.isString(style) ? { "text-align": style } : style
            );
            var frag = tmpRange.extractContents();
            p.appendChild(frag);
            tmpRange.insertNode(p);
            current = p;
          }
          current = domUtils.getNextDomNode(current, false, filterFn);
        } else {
          current = domUtils.getNextDomNode(current, true, filterFn);
        }
      }
      return range.moveToBookmark(bookmark2).moveToBookmark(bookmark);
    };

  UE.commands["justify"] = {
    execCommand: function(cmdName, align) {
      var range = this.selection.getRange(),
        txt;

      //闭合时单独处理
      if (range.collapsed) {
        txt = this.document.createTextNode("p");
        range.insertNode(txt);
      }
      doJustify(range, align);
      if (txt) {
        range.setStartBefore(txt).collapse(true);
        domUtils.remove(txt);
      }

      range.select();

      return true;
    },
    queryCommandValue: function() {
      var startNode = this.selection.getStart(),
        value = domUtils.getComputedStyle(startNode, "text-align");
      return defaultValue[value] ? value : "left";
    },
    queryCommandState: function() {
      var start = this.selection.getStart(),
        cell =
          start &&
          domUtils.findParentByTagName(start, ["td", "th", "caption"], true);

      return cell ? -1 : 0;
    }
  };
};


// plugins/font.js
/**
 * 字体颜色,背景色,字号,字体,下划线,删除线
 * @file
 * @since 1.2.6.1
 */

/**
 * 字体颜色
 * @command forecolor
 * @method execCommand
 * @param { String } cmd 命令字符串
 * @param { String } value 色值(必须十六进制)
 * @example
 * ```javascript
 * editor.execCommand( 'forecolor', '#000' );
 * ```
 */
/**
 * 返回选区字体颜色
 * @command forecolor
 * @method queryCommandValue
 * @param { String } cmd 命令字符串
 * @return { String } 返回字体颜色
 * @example
 * ```javascript
 * editor.queryCommandValue( 'forecolor' );
 * ```
 */

/**
 * 字体背景颜色
 * @command backcolor
 * @method execCommand
 * @param { String } cmd 命令字符串
 * @param { String } value 色值(必须十六进制)
 * @example
 * ```javascript
 * editor.execCommand( 'backcolor', '#000' );
 * ```
 */
/**
 * 返回选区字体颜色
 * @command backcolor
 * @method queryCommandValue
 * @param { String } cmd 命令字符串
 * @return { String } 返回字体背景颜色
 * @example
 * ```javascript
 * editor.queryCommandValue( 'backcolor' );
 * ```
 */

/**
 * 字体大小
 * @command fontsize
 * @method execCommand
 * @param { String } cmd 命令字符串
 * @param { String } value 字体大小
 * @example
 * ```javascript
 * editor.execCommand( 'fontsize', '14px' );
 * ```
 */
/**
 * 返回选区字体大小
 * @command fontsize
 * @method queryCommandValue
 * @param { String } cmd 命令字符串
 * @return { String } 返回字体大小
 * @example
 * ```javascript
 * editor.queryCommandValue( 'fontsize' );
 * ```
 */

/**
 * 字体样式
 * @command fontfamily
 * @method execCommand
 * @param { String } cmd 命令字符串
 * @param { String } value 字体样式
 * @example
 * ```javascript
 * editor.execCommand( 'fontfamily', '微软雅黑' );
 * ```
 */
/**
 * 返回选区字体样式
 * @command fontfamily
 * @method queryCommandValue
 * @param { String } cmd 命令字符串
 * @return { String } 返回字体样式
 * @example
 * ```javascript
 * editor.queryCommandValue( 'fontfamily' );
 * ```
 */

/**
 * 字体下划线,与删除线互斥
 * @command underline
 * @method execCommand
 * @param { String } cmd 命令字符串
 * @example
 * ```javascript
 * editor.execCommand( 'underline' );
 * ```
 */

/**
 * 字体删除线,与下划线互斥
 * @command strikethrough
 * @method execCommand
 * @param { String } cmd 命令字符串
 * @example
 * ```javascript
 * editor.execCommand( 'strikethrough' );
 * ```
 */

/**
 * 字体边框
 * @command fontborder
 * @method execCommand
 * @param { String } cmd 命令字符串
 * @example
 * ```javascript
 * editor.execCommand( 'fontborder' );
 * ```
 */

UE.plugins["font"] = function() {
  var me = this,
    fonts = {
      forecolor: "color",
      backcolor: "background-color",
      fontsize: "font-size",
      fontfamily: "font-family",
      underline: "text-decoration",
      strikethrough: "text-decoration",
      fontborder: "border"
    },
    lang = me.getLang(),
    needCmd = { underline: 1, strikethrough: 1, fontborder: 1 },
    needSetChild = {
      forecolor: "color",
      backcolor: "background-color",
      fontsize: "font-size",
      fontfamily: "font-family"
    };
  me.setOpt({
    fontfamily: [
      { name: "default", val: "default" },
      { name: "songti", val: "宋体,SimSun" },
      { name: "yahei", val: "微软雅黑,Microsoft YaHei" },
      { name: "kaiti", val: "楷体,楷体_GB2312,SimKai" },
      { name: "heiti", val: "黑体,SimHei" },
      { name: "lishu", val: "隶书,SimLi" },
      // { name: "andaleMono", val: "andale mono" },
      { name: "arial", val: "arial,helvetica,sans-serif" },
      // { name: "arialBlack", val: "arial black,avant garde" },
      // { name: "comicSansMs", val: "comic sans ms" },
      // { name: "impact", val: "impact,chicago" },
      { name: "timesNewRoman", val: "times new roman" }
    ],
    fontsize: [10, 11, 12, 14, 16, 18, 20, 24, 36]
  });

  function mergeWithParent(node) {
    var parent;
    while ((parent = node.parentNode)) {
      if (
        parent.tagName == "SPAN" &&
        domUtils.getChildCount(parent, function(child) {
          return !domUtils.isBookmarkNode(child) && !domUtils.isBr(child);
        }) == 1
      ) {
        parent.style.cssText += node.style.cssText;
        domUtils.remove(node, true);
        node = parent;
      } else {
        break;
      }
    }
  }
  function mergeChild(rng, cmdName, value) {
    if (needSetChild[cmdName]) {
      rng.adjustmentBoundary();
      if (!rng.collapsed && rng.startContainer.nodeType == 1) {
        var start = rng.startContainer.childNodes[rng.startOffset];
        if (start && domUtils.isTagNode(start, "span")) {
          var bk = rng.createBookmark();
          utils.each(domUtils.getElementsByTagName(start, "span"), function(
            span
          ) {
            if (!span.parentNode || domUtils.isBookmarkNode(span)) return;
            if (
              cmdName == "backcolor" &&
              domUtils
                .getComputedStyle(span, "background-color")
                .toLowerCase() === value
            ) {
              return;
            }
            domUtils.removeStyle(span, needSetChild[cmdName]);
            if (span.style.cssText.replace(/^\s+$/, "").length == 0) {
              domUtils.remove(span, true);
            }
          });
          rng.moveToBookmark(bk);
        }
      }
    }
  }
  function mergesibling(rng, cmdName, value) {
    var collapsed = rng.collapsed,
      bk = rng.createBookmark(),
      common;
    if (collapsed) {
      common = bk.start.parentNode;
      while (dtd.$inline[common.tagName]) {
        common = common.parentNode;
      }
    } else {
      common = domUtils.getCommonAncestor(bk.start, bk.end);
    }
    utils.each(domUtils.getElementsByTagName(common, "span"), function(span) {
      if (!span.parentNode || domUtils.isBookmarkNode(span)) return;
      if (/\s*border\s*:\s*none;?\s*/i.test(span.style.cssText)) {
        if (/^\s*border\s*:\s*none;?\s*$/.test(span.style.cssText)) {
          domUtils.remove(span, true);
        } else {
          domUtils.removeStyle(span, "border");
        }
        return;
      }
      if (
        /border/i.test(span.style.cssText) &&
        span.parentNode.tagName == "SPAN" &&
        /border/i.test(span.parentNode.style.cssText)
      ) {
        span.style.cssText = span.style.cssText.replace(
          /border[^:]*:[^;]+;?/gi,
          ""
        );
      }
      if (!(cmdName == "fontborder" && value == "none")) {
        var next = span.nextSibling;
        while (next && next.nodeType == 1 && next.tagName == "SPAN") {
          if (domUtils.isBookmarkNode(next) && cmdName == "fontborder") {
            span.appendChild(next);
            next = span.nextSibling;
            continue;
          }
          if (next.style.cssText == span.style.cssText) {
            domUtils.moveChild(next, span);
            domUtils.remove(next);
          }
          if (span.nextSibling === next) break;
          next = span.nextSibling;
        }
      }

      mergeWithParent(span);
      if (browser.ie && browser.version > 8) {
        //拷贝父亲们的特别的属性,这里只做背景颜色的处理
        var parent = domUtils.findParent(span, function(n) {
          return (
            n.tagName == "SPAN" && /background-color/.test(n.style.cssText)
          );
        });
        if (parent && !/background-color/.test(span.style.cssText)) {
          span.style.backgroundColor = parent.style.backgroundColor;
        }
      }
    });
    rng.moveToBookmark(bk);
    mergeChild(rng, cmdName, value);
  }

  me.addInputRule(function(root) {
    utils.each(root.getNodesByTagName("u s del font strike"), function(node) {
      if (node.tagName == "font") {
        var cssStyle = [];
        for (var p in node.attrs) {
          switch (p) {
            case "size":
              cssStyle.push(
                "font-size:" +
                  ({
                    "1": "10",
                    "2": "12",
                    "3": "16",
                    "4": "18",
                    "5": "24",
                    "6": "32",
                    "7": "48"
                  }[node.attrs[p]] || node.attrs[p]) +
                  "px"
              );
              break;
            case "color":
              cssStyle.push("color:" + node.attrs[p]);
              break;
            case "face":
              cssStyle.push("font-family:" + node.attrs[p]);
              break;
            case "style":
              cssStyle.push(node.attrs[p]);
          }
        }
        node.attrs = {
          style: cssStyle.join(";")
        };
      } else {
        var val = node.tagName == "u" ? "underline" : "line-through";
        node.attrs = {
          style: (node.getAttr("style") || "") + "text-decoration:" + val + ";"
        };
      }
      node.tagName = "span";
    });
    //        utils.each(root.getNodesByTagName('span'), function (node) {
    //            var val;
    //            if(val = node.getAttr('class')){
    //                if(/fontstrikethrough/.test(val)){
    //                    node.setStyle('text-decoration','line-through');
    //                    if(node.attrs['class']){
    //                        node.attrs['class'] = node.attrs['class'].replace(/fontstrikethrough/,'');
    //                    }else{
    //                        node.setAttr('class')
    //                    }
    //                }
    //                if(/fontborder/.test(val)){
    //                    node.setStyle('border','1px solid #000');
    //                    if(node.attrs['class']){
    //                        node.attrs['class'] = node.attrs['class'].replace(/fontborder/,'');
    //                    }else{
    //                        node.setAttr('class')
    //                    }
    //                }
    //            }
    //        });
  });
  //    me.addOutputRule(function(root){
  //        utils.each(root.getNodesByTagName('span'), function (node) {
  //            var val;
  //            if(val = node.getStyle('text-decoration')){
  //                if(/line-through/.test(val)){
  //                    if(node.attrs['class']){
  //                        node.attrs['class'] += ' fontstrikethrough';
  //                    }else{
  //                        node.setAttr('class','fontstrikethrough')
  //                    }
  //                }
  //
  //                node.setStyle('text-decoration')
  //            }
  //            if(val = node.getStyle('border')){
  //                if(/1px/.test(val) && /solid/.test(val)){
  //                    if(node.attrs['class']){
  //                        node.attrs['class'] += ' fontborder';
  //
  //                    }else{
  //                        node.setAttr('class','fontborder')
  //                    }
  //                }
  //                node.setStyle('border')
  //
  //            }
  //        });
  //    });
  for (var p in fonts) {
    (function(cmd, style) {
      UE.commands[cmd] = {
        execCommand: function(cmdName, value) {
          value =
            value ||
            (this.queryCommandState(cmdName)
              ? "none"
              : cmdName === "underline"
                ? "underline"
                : cmdName === "fontborder" ? "1px solid #000" : "line-through");
          var me = this,
            range = this.selection.getRange(),
            text;

          if (value === "default") {
            if (range.collapsed) {
              text = me.document.createTextNode("font");
              range.insertNode(text).select();
            }
            me.execCommand("removeFormat", "span,a", style);
            if (text) {
              range.setStartBefore(text).collapse(true);
              domUtils.remove(text);
            }
            mergesibling(range, cmdName, value);
            range.select();
          } else {
            if (!range.collapsed) {
              if (needCmd[cmd] && me.queryCommandValue(cmd)) {
                me.execCommand("removeFormat", "span,a", style);
              }
              range = me.selection.getRange();

              range.applyInlineStyle("span", { style: style + ":" + value });
              mergesibling(range, cmdName, value);
              range.select();
            } else {
              var span = domUtils.findParentByTagName(
                range.startContainer,
                "span",
                true
              );
              text = me.document.createTextNode("font");
              if (
                span &&
                !span.children.length &&
                !span[browser.ie ? "innerText" : "textContent"].replace(
                  fillCharReg,
                  ""
                ).length
              ) {
                //for ie hack when enter
                range.insertNode(text);
                if (needCmd[cmd]) {
                  range.selectNode(text).select();
                  me.execCommand("removeFormat", "span,a", style, null);

                  span = domUtils.findParentByTagName(text, "span", true);
                  range.setStartBefore(text);
                }
                span && (span.style.cssText += ";" + style + ":" + value);
                range.collapse(true).select();
              } else {
                range.insertNode(text);
                range.selectNode(text).select();
                span = range.document.createElement("span");

                if (needCmd[cmd]) {
                  //a标签内的不处理跳过
                  if (domUtils.findParentByTagName(text, "a", true)) {
                    range.setStartBefore(text).setCursor();
                    domUtils.remove(text);
                    return;
                  }
                  me.execCommand("removeFormat", "span,a", style);
                }

                span.style.cssText = style + ":" + value;

                text.parentNode.insertBefore(span, text);
                //修复，span套span 但样式不继承的问题
                if (!browser.ie || (browser.ie && browser.version == 9)) {
                  var spanParent = span.parentNode;
                  while (!domUtils.isBlockElm(spanParent)) {
                    if (spanParent.tagName == "SPAN") {
                      //opera合并style不会加入";"
                      span.style.cssText =
                        spanParent.style.cssText + ";" + span.style.cssText;
                    }
                    spanParent = spanParent.parentNode;
                  }
                }

                if (opera) {
                  setTimeout(function() {
                    range.setStart(span, 0).collapse(true);
                    mergesibling(range, cmdName, value);
                    range.select();
                  });
                } else {
                  range.setStart(span, 0).collapse(true);
                  mergesibling(range, cmdName, value);
                  range.select();
                }

                //trace:981
                //domUtils.mergeToParent(span)
              }
              domUtils.remove(text);
            }
          }
          return true;
        },
        queryCommandValue: function(cmdName) {
          var startNode = this.selection.getStart();
          var styleVal;

          //trace:946
          if (cmdName === "underline" || cmdName === "strikethrough") {
            var tmpNode = startNode,
              value;
            while (
              tmpNode &&
              !domUtils.isBlockElm(tmpNode) &&
              !domUtils.isBody(tmpNode)
            ) {
              if (tmpNode.nodeType == 1) {
                value = domUtils.getComputedStyle(tmpNode, style);
                if (value != "none") {
                  return value;
                }
              }

              tmpNode = tmpNode.parentNode;
            }
            return "none";
          }

          else if (cmdName === "fontborder") {
            var tmp = startNode,
              val;
            while (tmp && dtd.$inline[tmp.tagName]) {
              if ((val = domUtils.getComputedStyle(tmp, "border"))) {
                if (/1px/.test(val) && /solid/.test(val)) {
                  return val;
                }
              }
              tmp = tmp.parentNode;
            }
            return "";
          }

          else if (cmdName === "FontSize") {
            styleVal = domUtils.getComputedStyle(startNode, style);
            tmp = /^([\d\.]+)(\w+)$/.exec(styleVal);

            if (tmp) {
              return Math.floor(tmp[1]) + tmp[2];
            }

            return styleVal;
          }

          else if(cmdName === 'FontFamily'){
            styleVal = domUtils.getComputedStyle(startNode, style)
            // 移除左右引号
            styleVal = styleVal.replace(/['"]/g, '');
            var fontFamily = lang.fontfamily.default;
            var fontList = me.options["fontfamily"] || [];
            for(var i=0;i<fontList.length;i++){
              var v = fontList[i];
              // console.log('FontFamily',styleVal, v.val);
              if(v.val === styleVal){
                fontFamily = styleVal;
                break;
              }
            }
            // console.log('FontFamily',styleVal, fontFamily);
            return fontFamily;
          }

          value = domUtils.getComputedStyle(startNode, style);
          return value;
        },
        queryCommandState: function(cmdName) {
          if (!needCmd[cmdName]) return 0;
          var val = this.queryCommandValue(cmdName);
          if (cmdName == "fontborder") {
            return /1px/.test(val) && /solid/.test(val);
          } else {
            return cmdName == "underline"
              ? /underline/.test(val)
              : /line\-through/.test(val);
          }
        }
      };
    })(p, fonts[p]);
  }
};


// plugins/link.js
/**
 * 超链接
 * @file
 * @since 1.2.6.1
 */

/**
 * 插入超链接
 * @command link
 * @method execCommand
 * @param { String } cmd 命令字符串
 * @param { Object } options   设置自定义属性，例如：url、title、target
 * @example
 * ```javascript
 * editor.execCommand( 'link', '{
 *     url:'ueditor.baidu.com',
 *     title:'ueditor',
 *     target:'_blank'
 * }' );
 * ```
 */
/**
 * 返回当前选中的第一个超链接节点
 * @command link
 * @method queryCommandValue
 * @param { String } cmd 命令字符串
 * @return { Element } 超链接节点
 * @example
 * ```javascript
 * editor.queryCommandValue( 'link' );
 * ```
 */

/**
 * 取消超链接
 * @command unlink
 * @method execCommand
 * @param { String } cmd 命令字符串
 * @example
 * ```javascript
 * editor.execCommand( 'unlink');
 * ```
 */

UE.plugins["link"] = function() {
  function optimize(range) {
    var start = range.startContainer,
      end = range.endContainer;

    if ((start = domUtils.findParentByTagName(start, "a", true))) {
      range.setStartBefore(start);
    }
    if ((end = domUtils.findParentByTagName(end, "a", true))) {
      range.setEndAfter(end);
    }
  }

  UE.commands["unlink"] = {
    execCommand: function() {
      var range = this.selection.getRange(),
        bookmark;
      if (
        range.collapsed &&
        !domUtils.findParentByTagName(range.startContainer, "a", true)
      ) {
        return;
      }
      bookmark = range.createBookmark();
      optimize(range);
      range.removeInlineStyle("a").moveToBookmark(bookmark).select();
    },
    queryCommandState: function() {
      return !this.highlight && this.queryCommandValue("link") ? 0 : -1;
    }
  };
  function doLink(range, opt, me) {
    var rngClone = range.cloneRange(),
      link = me.queryCommandValue("link");
    optimize((range = range.adjustmentBoundary()));
    var start = range.startContainer;
    if (start.nodeType == 1 && link) {
      start = start.childNodes[range.startOffset];
      if (
        start &&
        start.nodeType == 1 &&
        start.tagName == "A" &&
        /^(?:https?|ftp|file)\s*:\s*\/\//.test(
          start[browser.ie ? "innerText" : "textContent"]
        )
      ) {
        start[browser.ie ? "innerText" : "textContent"] = utils.html(
          opt.textValue || opt.href
        );
      }
    }
    if (!rngClone.collapsed || link) {
      range.removeInlineStyle("a");
      rngClone = range.cloneRange();
    }

    if (rngClone.collapsed) {
      var a = range.document.createElement("a"),
        text = "";
      if (opt.textValue) {
        text = utils.html(opt.textValue);
        delete opt.textValue;
      } else {
        text = utils.html(opt.href);
      }
      domUtils.setAttributes(a, opt);
      start = domUtils.findParentByTagName(rngClone.startContainer, "a", true);
      if (start && domUtils.isInNodeEndBoundary(rngClone, start)) {
        range.setStartAfter(start).collapse(true);
      }
      a[browser.ie ? "innerText" : "textContent"] = text;
      range.insertNode(a).selectNode(a);
    } else {
      range.applyInlineStyle("a", opt);
    }
  }
  UE.commands["link"] = {
    execCommand: function(cmdName, opt) {
      var range;
      opt._href && (opt._href = utils.unhtml(opt._href, /[<">]/g));
      opt.href && (opt.href = utils.unhtml(opt.href, /[<">]/g));
      opt.textValue && (opt.textValue = utils.unhtml(opt.textValue, /[<">]/g));
      doLink((range = this.selection.getRange()), opt, this);
      //闭合都不加占位符，如果加了会在a后边多个占位符节点，导致a是图片背景组成的列表，出现空白问题
      range.collapse().select(true);
    },
    queryCommandValue: function() {
      var range = this.selection.getRange(),
        node;
      if (range.collapsed) {
        //                    node = this.selection.getStart();
        //在ie下getstart()取值偏上了
        node = range.startContainer;
        node = node.nodeType == 1 ? node : node.parentNode;

        if (
          node &&
          (node = domUtils.findParentByTagName(node, "a", true)) &&
          !domUtils.isInNodeEndBoundary(range, node)
        ) {
          return node;
        }
      } else {
        //trace:1111  如果是<p><a>xx</a></p> startContainer是p就会找不到a
        range.shrinkBoundary();
        var start = range.startContainer.nodeType == 3 ||
          !range.startContainer.childNodes[range.startOffset]
          ? range.startContainer
          : range.startContainer.childNodes[range.startOffset],
          end = range.endContainer.nodeType == 3 || range.endOffset == 0
            ? range.endContainer
            : range.endContainer.childNodes[range.endOffset - 1],
          common = range.getCommonAncestor();
        node = domUtils.findParentByTagName(common, "a", true);
        if (!node && common.nodeType == 1) {
          var as = common.getElementsByTagName("a"),
            ps,
            pe;

          for (var i = 0, ci; (ci = as[i++]); ) {
            (ps = domUtils.getPosition(ci, start)), (pe = domUtils.getPosition(
              ci,
              end
            ));
            if (
              (ps & domUtils.POSITION_FOLLOWING ||
                ps & domUtils.POSITION_CONTAINS) &&
              (pe & domUtils.POSITION_PRECEDING ||
                pe & domUtils.POSITION_CONTAINS)
            ) {
              node = ci;
              break;
            }
          }
        }
        return node;
      }
    },
    queryCommandState: function() {
      //判断如果是视频的话连接不可用
      //fix 853
      var img = this.selection.getRange().getClosedNode(),
        flag =
          img &&
          (img.className == "edui-faked-video" ||
            img.className.indexOf("edui-upload-video") != -1);
      return flag ? -1 : 0;
    }
  };
};


// plugins/iframe.js
///import core
///import plugins\inserthtml.js
///commands 插入框架
///commandsName  InsertFrame
///commandsTitle  插入Iframe
///commandsDialog  dialogs\insertframe

UE.plugins["insertframe"] = function() {
  var me = this;
  function deleteIframe() {
    me._iframe && delete me._iframe;
  }

  me.addListener("selectionchange", function() {
    deleteIframe();
  });
};


// plugins/scrawl.js
///import core
///commands 涂鸦
///commandsName  Scrawl
///commandsTitle  涂鸦
///commandsDialog  dialogs\scrawl
UE.commands["scrawl"] = {
  queryCommandState: function() {
    return browser.ie && browser.version <= 8 ? -1 : 0;
  }
};


// plugins/removeformat.js
/**
 * 清除格式
 * @file
 * @since 1.2.6.1
 */

/**
 * 清除文字样式
 * @command removeformat
 * @method execCommand
 * @param { String } cmd 命令字符串
 * @param   {String}   tags     以逗号隔开的标签。如：strong
 * @param   {String}   style    样式如：color
 * @param   {String}   attrs    属性如:width
 * @example
 * ```javascript
 * editor.execCommand( 'removeformat', 'strong','color','width' );
 * ```
 */

UE.plugins["removeformat"] = function() {
  var me = this;
  me.setOpt({
    removeFormatTags:
      "b,big,code,del,dfn,em,font,i,ins,kbd,q,samp,small,span,strike,strong,sub,sup,tt,u,var",
    removeFormatAttributes: "class,style,lang,width,height,align,hspace,valign"
  });
  me.commands["removeformat"] = {
    execCommand: function(cmdName, tags, style, attrs, notIncludeA) {
      var tagReg = new RegExp(
        "^(?:" +
          (tags || this.options.removeFormatTags).replace(/,/g, "|") +
          ")$",
        "i"
      ),
        removeFormatAttributes = style
          ? []
          : (attrs || this.options.removeFormatAttributes).split(","),
        range = new dom.Range(this.document),
        bookmark,
        node,
        parent,
        filter = function(node) {
          return node.nodeType == 1;
        };

      function isRedundantSpan(node) {
        if (node.nodeType == 3 || node.tagName.toLowerCase() != "span") {
          return 0;
        }
        if (browser.ie) {
          //ie 下判断实效，所以只能简单用style来判断
          //return node.style.cssText == '' ? 1 : 0;
          var attrs = node.attributes;
          if (attrs.length) {
            for (var i = 0, l = attrs.length; i < l; i++) {
              if (attrs[i].specified) {
                return 0;
              }
            }
            return 1;
          }
        }
        return !node.attributes.length;
      }
      function doRemove(range) {
        var bookmark1 = range.createBookmark();
        if (range.collapsed) {
          range.enlarge(true);
        }

        //不能把a标签切了
        if (!notIncludeA) {
          var aNode = domUtils.findParentByTagName(
            range.startContainer,
            "a",
            true
          );
          if (aNode) {
            range.setStartBefore(aNode);
          }

          aNode = domUtils.findParentByTagName(range.endContainer, "a", true);
          if (aNode) {
            range.setEndAfter(aNode);
          }
        }

        bookmark = range.createBookmark();

        node = bookmark.start;

        //切开始
        while ((parent = node.parentNode) && !domUtils.isBlockElm(parent)) {
          domUtils.breakParent(node, parent);

          domUtils.clearEmptySibling(node);
        }
        if (bookmark.end) {
          //切结束
          node = bookmark.end;
          while ((parent = node.parentNode) && !domUtils.isBlockElm(parent)) {
            domUtils.breakParent(node, parent);
            domUtils.clearEmptySibling(node);
          }

          //开始去除样式
          var current = domUtils.getNextDomNode(bookmark.start, false, filter),
            next;
          while (current) {
            if (current == bookmark.end) {
              break;
            }

            next = domUtils.getNextDomNode(current, true, filter);

            if (
              !dtd.$empty[current.tagName.toLowerCase()] &&
              !domUtils.isBookmarkNode(current)
            ) {
              if (tagReg.test(current.tagName)) {
                if (style) {
                  domUtils.removeStyle(current, style);
                  if (isRedundantSpan(current) && style != "text-decoration") {
                    domUtils.remove(current, true);
                  }
                } else {
                  domUtils.remove(current, true);
                }
              } else {
                //trace:939  不能把list上的样式去掉
                if (
                  !dtd.$tableContent[current.tagName] &&
                  !dtd.$list[current.tagName]
                ) {
                  domUtils.removeAttributes(current, removeFormatAttributes);
                  if (isRedundantSpan(current)) {
                    domUtils.remove(current, true);
                  }
                }
              }
            }
            current = next;
          }
        }
        //trace:1035
        //trace:1096 不能把td上的样式去掉，比如边框
        var pN = bookmark.start.parentNode;
        if (
          domUtils.isBlockElm(pN) &&
          !dtd.$tableContent[pN.tagName] &&
          !dtd.$list[pN.tagName]
        ) {
          domUtils.removeAttributes(pN, removeFormatAttributes);
        }
        pN = bookmark.end.parentNode;
        if (
          bookmark.end &&
          domUtils.isBlockElm(pN) &&
          !dtd.$tableContent[pN.tagName] &&
          !dtd.$list[pN.tagName]
        ) {
          domUtils.removeAttributes(pN, removeFormatAttributes);
        }
        range.moveToBookmark(bookmark).moveToBookmark(bookmark1);
        //清除冗余的代码 <b><bookmark></b>
        var node = range.startContainer,
          tmp,
          collapsed = range.collapsed;
        while (
          node.nodeType == 1 &&
          domUtils.isEmptyNode(node) &&
          dtd.$removeEmpty[node.tagName]
        ) {
          tmp = node.parentNode;
          range.setStartBefore(node);
          //trace:937
          //更新结束边界
          if (range.startContainer === range.endContainer) {
            range.endOffset--;
          }
          domUtils.remove(node);
          node = tmp;
        }

        if (!collapsed) {
          node = range.endContainer;
          while (
            node.nodeType == 1 &&
            domUtils.isEmptyNode(node) &&
            dtd.$removeEmpty[node.tagName]
          ) {
            tmp = node.parentNode;
            range.setEndBefore(node);
            domUtils.remove(node);

            node = tmp;
          }
        }
      }

      range = this.selection.getRange();
      doRemove(range);
      range.select();
    }
  };
};


// plugins/blockquote.js
/**
 * 添加引用
 * @file
 * @since 1.2.6.1
 */

/**
 * 添加引用
 * @command blockquote
 * @method execCommand
 * @param { String } cmd 命令字符串
 * @example
 * ```javascript
 * editor.execCommand( 'blockquote' );
 * ```
 */

/**
 * 添加引用
 * @command blockquote
 * @method execCommand
 * @param { String } cmd 命令字符串
 * @param { Object } attrs 节点属性
 * @example
 * ```javascript
 * editor.execCommand( 'blockquote',{
 *     style: "color: red;"
 * } );
 * ```
 */

UE.plugins["blockquote"] = function() {
  var me = this;
  function getObj(editor) {
    return domUtils.filterNodeList(
      editor.selection.getStartElementPath(),
      "blockquote"
    );
  }
  me.commands["blockquote"] = {
    execCommand: function(cmdName, attrs) {
      var range = this.selection.getRange(),
        obj = getObj(this),
        blockquote = dtd.blockquote,
        bookmark = range.createBookmark();

      if (obj) {
        var start = range.startContainer,
          startBlock = domUtils.isBlockElm(start)
            ? start
            : domUtils.findParent(start, function(node) {
                return domUtils.isBlockElm(node);
              }),
          end = range.endContainer,
          endBlock = domUtils.isBlockElm(end)
            ? end
            : domUtils.findParent(end, function(node) {
                return domUtils.isBlockElm(node);
              });

        //处理一下li
        startBlock =
          domUtils.findParentByTagName(startBlock, "li", true) || startBlock;
        endBlock =
          domUtils.findParentByTagName(endBlock, "li", true) || endBlock;

        if (
          startBlock.tagName == "LI" ||
          startBlock.tagName == "TD" ||
          startBlock === obj ||
          domUtils.isBody(startBlock)
        ) {
          domUtils.remove(obj, true);
        } else {
          domUtils.breakParent(startBlock, obj);
        }

        if (startBlock !== endBlock) {
          obj = domUtils.findParentByTagName(endBlock, "blockquote");
          if (obj) {
            if (
              endBlock.tagName == "LI" ||
              endBlock.tagName == "TD" ||
              domUtils.isBody(endBlock)
            ) {
              obj.parentNode && domUtils.remove(obj, true);
            } else {
              domUtils.breakParent(endBlock, obj);
            }
          }
        }

        var blockquotes = domUtils.getElementsByTagName(
          this.document,
          "blockquote"
        );
        for (var i = 0, bi; (bi = blockquotes[i++]); ) {
          if (!bi.childNodes.length) {
            domUtils.remove(bi);
          } else if (
            domUtils.getPosition(bi, startBlock) &
              domUtils.POSITION_FOLLOWING &&
            domUtils.getPosition(bi, endBlock) & domUtils.POSITION_PRECEDING
          ) {
            domUtils.remove(bi, true);
          }
        }
      } else {
        var tmpRange = range.cloneRange(),
          node = tmpRange.startContainer.nodeType == 1
            ? tmpRange.startContainer
            : tmpRange.startContainer.parentNode,
          preNode = node,
          doEnd = 1;

        //调整开始
        while (1) {
          if (domUtils.isBody(node)) {
            if (preNode !== node) {
              if (range.collapsed) {
                tmpRange.selectNode(preNode);
                doEnd = 0;
              } else {
                tmpRange.setStartBefore(preNode);
              }
            } else {
              tmpRange.setStart(node, 0);
            }

            break;
          }
          if (!blockquote[node.tagName]) {
            if (range.collapsed) {
              tmpRange.selectNode(preNode);
            } else {
              tmpRange.setStartBefore(preNode);
            }
            break;
          }

          preNode = node;
          node = node.parentNode;
        }

        //调整结束
        if (doEnd) {
          preNode = node = node = tmpRange.endContainer.nodeType == 1
            ? tmpRange.endContainer
            : tmpRange.endContainer.parentNode;
          while (1) {
            if (domUtils.isBody(node)) {
              if (preNode !== node) {
                tmpRange.setEndAfter(preNode);
              } else {
                tmpRange.setEnd(node, node.childNodes.length);
              }

              break;
            }
            if (!blockquote[node.tagName]) {
              tmpRange.setEndAfter(preNode);
              break;
            }

            preNode = node;
            node = node.parentNode;
          }
        }

        node = range.document.createElement("blockquote");
        domUtils.setAttributes(node, attrs);
        node.appendChild(tmpRange.extractContents());
        tmpRange.insertNode(node);
        //去除重复的
        var childs = domUtils.getElementsByTagName(node, "blockquote");
        for (var i = 0, ci; (ci = childs[i++]); ) {
          if (ci.parentNode) {
            domUtils.remove(ci, true);
          }
        }
      }
      range.moveToBookmark(bookmark).select();
    },
    queryCommandState: function() {
      return getObj(this) ? 1 : 0;
    }
  };
};


// plugins/convertcase.js
/**
 * 大小写转换
 * @file
 * @since 1.2.6.1
 */

/**
 * 把选区内文本变大写，与“tolowercase”命令互斥
 * @command touppercase
 * @method execCommand
 * @param { String } cmd 命令字符串
 * @example
 * ```javascript
 * editor.execCommand( 'touppercase' );
 * ```
 */

/**
 * 把选区内文本变小写，与“touppercase”命令互斥
 * @command tolowercase
 * @method execCommand
 * @param { String } cmd 命令字符串
 * @example
 * ```javascript
 * editor.execCommand( 'tolowercase' );
 * ```
 */
UE.commands["touppercase"] = UE.commands["tolowercase"] = {
  execCommand: function(cmd) {
    var me = this;
    var rng = me.selection.getRange();
    if (rng.collapsed) {
      return rng;
    }
    var bk = rng.createBookmark(),
      bkEnd = bk.end,
      filterFn = function(node) {
        return !domUtils.isBr(node) && !domUtils.isWhitespace(node);
      },
      curNode = domUtils.getNextDomNode(bk.start, false, filterFn);
    while (
      curNode &&
      domUtils.getPosition(curNode, bkEnd) & domUtils.POSITION_PRECEDING
    ) {
      if (curNode.nodeType == 3) {
        curNode.nodeValue = curNode.nodeValue[
          cmd == "touppercase" ? "toUpperCase" : "toLowerCase"
        ]();
      }
      curNode = domUtils.getNextDomNode(curNode, true, filterFn);
      if (curNode === bkEnd) {
        break;
      }
    }
    rng.moveToBookmark(bk).select();
  }
};


// plugins/indent.js
/**
 * 首行缩进
 * @file
 * @since 1.2.6.1
 */

/**
 * 缩进
 * @command indent
 * @method execCommand
 * @param { String } cmd 命令字符串
 * @example
 * ```javascript
 * editor.execCommand( 'indent' );
 * ```
 */
UE.commands["indent"] = {
  execCommand: function() {
    var me = this,
      value = me.queryCommandState("indent")
        ? "0em"
        : me.options.indentValue || "2em";
    me.execCommand("Paragraph", "p", { style: "text-indent:" + value });
  },
  queryCommandState: function() {
    var pN = domUtils.filterNodeList(
      this.selection.getStartElementPath(),
      "p h1 h2 h3 h4 h5 h6"
    );
    return pN && pN.style.textIndent && parseInt(pN.style.textIndent) ? 1 : 0;
  }
};


// plugins/print.js
/**
 * 打印
 * @file
 * @since 1.2.6.1
 */

/**
 * 打印
 * @command print
 * @method execCommand
 * @param { String } cmd 命令字符串
 * @example
 * ```javascript
 * editor.execCommand( 'print' );
 * ```
 */
UE.commands["print"] = {
  execCommand: function() {
    this.window.print();
  },
  notNeedUndo: 1
};


// plugins/preview.js
/**
 * 预览
 * @file
 * @since 1.2.6.1
 */

/**
 * 预览
 * @command preview
 * @method execCommand
 * @param { String } cmd 命令字符串
 * @example
 * ```javascript
 * editor.execCommand( 'preview' );
 * ```
 */
UE.commands["preview"] = {
  execCommand: function() {
    var w = window.open("", "_blank", ""),
      d = w.document;
    d.open();
    d.write(
      '<!DOCTYPE html><html><head><meta charset="utf-8"/><script src="' +
        this.options.UEDITOR_HOME_URL +
        'ueditor.parse.js"></script><script>' +
        "setTimeout(function(){uParse('div',{rootPath: '" +
        this.options.UEDITOR_HOME_URL +
        "'})},300)" +
        "</script></head><body><div>" +
        this.getContent(null, null, true) +
        "</div></body></html>"
    );
    d.close();
  },
  notNeedUndo: 1
};


// plugins/selectall.js
/**
 * 全选
 * @file
 * @since 1.2.6.1
 */

/**
 * 选中所有内容
 * @command selectall
 * @method execCommand
 * @param { String } cmd 命令字符串
 * @example
 * ```javascript
 * editor.execCommand( 'selectall' );
 * ```
 */
UE.plugins["selectall"] = function() {
  var me = this;
  me.commands["selectall"] = {
    execCommand: function() {
      //去掉了原生的selectAll,因为会出现报错和当内容为空时，不能出现闭合状态的光标
      var me = this,
        body = me.body,
        range = me.selection.getRange();
      range.selectNodeContents(body);
      if (domUtils.isEmptyBlock(body)) {
        //opera不能自动合并到元素的里边，要手动处理一下
        if (browser.opera && body.firstChild && body.firstChild.nodeType == 1) {
          range.setStartAtFirst(body.firstChild);
        }
        range.collapse(true);
      }
      range.select(true);
    },
    notNeedUndo: 1
  };

  //快捷键
  me.addshortcutkey({
    selectAll: "ctrl+65"
  });
};


// plugins/paragraph.js
/**
 * 段落样式
 * @file
 * @since 1.2.6.1
 */

/**
 * 段落格式
 * @command paragraph
 * @method execCommand
 * @param { String } cmd 命令字符串
 * @param {String}   style               标签值为：'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'
 * @param {Object}   attrs               标签的属性
 * @example
 * ```javascript
 * editor.execCommand( 'Paragraph','h1','{
 *     class:'test'
 * }' );
 * ```
 */

/**
 * 返回选区内节点标签名
 * @command paragraph
 * @method queryCommandValue
 * @param { String } cmd 命令字符串
 * @return { String } 节点标签名
 * @example
 * ```javascript
 * editor.queryCommandValue( 'Paragraph' );
 * ```
 */

UE.plugins["paragraph"] = function() {
  var me = this,
    block = domUtils.isBlockElm,
    notExchange = ["TD", "LI", "PRE"],
    doParagraph = function(range, style, attrs, sourceCmdName) {
      var bookmark = range.createBookmark(),
        filterFn = function(node) {
          return node.nodeType == 1
            ? node.tagName.toLowerCase() != "br" &&
                !domUtils.isBookmarkNode(node)
            : !domUtils.isWhitespace(node);
        },
        para;

      range.enlarge(true);
      var bookmark2 = range.createBookmark(),
        current = domUtils.getNextDomNode(bookmark2.start, false, filterFn),
        tmpRange = range.cloneRange(),
        tmpNode;
      while (
        current &&
        !(
          domUtils.getPosition(current, bookmark2.end) &
          domUtils.POSITION_FOLLOWING
        )
      ) {
        if (current.nodeType === 3 || !block(current)) {
          tmpRange.setStartBefore(current);
          while (current && current !== bookmark2.end && !block(current)) {
            tmpNode = current;
            current = domUtils.getNextDomNode(current, false, null, function(
              node
            ) {
              return !block(node);
            });
          }
          tmpRange.setEndAfter(tmpNode);

          para = range.document.createElement(style);
          if (attrs) {
            domUtils.setAttributes(para, attrs);
            if (
              sourceCmdName &&
              sourceCmdName === "customstyle" &&
              attrs.style
            ) {
              para.style.cssText = attrs.style;
            }
          }
          para.appendChild(tmpRange.extractContents());
          //需要内容占位
          if (domUtils.isEmptyNode(para)) {
            domUtils.fillChar(range.document, para);
          }

          tmpRange.insertNode(para);

          var parent = para.parentNode;
          //如果para上一级是一个block元素且不是body,td就删除它
          if (
            block(parent) &&
            !domUtils.isBody(para.parentNode) &&
            utils.indexOf(notExchange, parent.tagName) === -1
          ) {
            //存储dir,style
            if (!(sourceCmdName && sourceCmdName === "customstyle")) {
              parent.getAttribute("dir") &&
                para.setAttribute("dir", parent.getAttribute("dir"));
              //trace:1070
              parent.style.cssText &&
                (para.style.cssText =
                  parent.style.cssText + ";" + para.style.cssText);
              //trace:1030
              parent.style.textAlign &&
                !para.style.textAlign &&
                (para.style.textAlign = parent.style.textAlign);
              parent.style.textIndent &&
                !para.style.textIndent &&
                (para.style.textIndent = parent.style.textIndent);
              parent.style.padding &&
                !para.style.padding &&
                (para.style.padding = parent.style.padding);
            }

            //trace:1706 选择的就是h1-6要删除
            if (
              attrs &&
              /h\d/i.test(parent.tagName) &&
              !/h\d/i.test(para.tagName)
            ) {
              domUtils.setAttributes(parent, attrs);
              if (
                sourceCmdName &&
                sourceCmdName === "customstyle" &&
                attrs.style
              ) {
                parent.style.cssText = attrs.style;
              }
              domUtils.remove(para.parentNode, true);
              para = parent;
            } else {
              domUtils.remove(para.parentNode, true);
            }
          }
          if (utils.indexOf(notExchange, parent.tagName) !== -1) {
            current = parent;
          } else {
            current = para;
          }

          current = domUtils.getNextDomNode(current, false, filterFn);
        } else {
          current = domUtils.getNextDomNode(current, true, filterFn);
        }
      }
      return range.moveToBookmark(bookmark2).moveToBookmark(bookmark);
    };
  me.setOpt("paragraph", {
    p: "",
    h1: "",
    h2: "",
    h3: "",
    h4: "",
    h5: "",
    h6: ""
  });
  me.commands["paragraph"] = {
    execCommand: function(cmdName, style, attrs, sourceCmdName) {
      var range = this.selection.getRange();
      //闭合时单独处理
      if (range.collapsed) {
        var txt = this.document.createTextNode("p");
        range.insertNode(txt);
        //去掉冗余的fillchar
        if (browser.ie) {
          var node = txt.previousSibling;
          if (node && domUtils.isWhitespace(node)) {
            domUtils.remove(node);
          }
          node = txt.nextSibling;
          if (node && domUtils.isWhitespace(node)) {
            domUtils.remove(node);
          }
        }
      }
      range = doParagraph(range, style, attrs, sourceCmdName);
      if (txt) {
        range.setStartBefore(txt).collapse(true);
        pN = txt.parentNode;

        domUtils.remove(txt);

        if (domUtils.isBlockElm(pN) && domUtils.isEmptyNode(pN)) {
          domUtils.fillNode(this.document, pN);
        }
      }

      if (
        browser.gecko &&
        range.collapsed &&
        range.startContainer.nodeType === 1
      ) {
        var child = range.startContainer.childNodes[range.startOffset];
        if (
          child &&
          child.nodeType === 1 &&
          child.tagName.toLowerCase() === style
        ) {
          range.setStart(child, 0).collapse(true);
        }
      }
      //trace:1097 原来有true，原因忘了，但去了就不能清除多余的占位符了
      range.select();

      return true;
    },
    queryCommandValue: function() {
      var node = domUtils.filterNodeList(
        this.selection.getStartElementPath(),
        "p h1 h2 h3 h4 h5 h6"
      );
      return node ? node.tagName.toLowerCase() : "";
    }
  };
};


// plugins/directionality.js
/**
 * 设置文字输入的方向的插件
 * @file
 * @since 1.2.6.1
 */
(function() {
  var block = domUtils.isBlockElm,
    getObj = function(editor) {
      //            var startNode = editor.selection.getStart(),
      //                parents;
      //            if ( startNode ) {
      //                //查找所有的是block的父亲节点
      //                parents = domUtils.findParents( startNode, true, block, true );
      //                for ( var i = 0,ci; ci = parents[i++]; ) {
      //                    if ( ci.getAttribute( 'dir' ) ) {
      //                        return ci;
      //                    }
      //                }
      //            }
      return domUtils.filterNodeList(
        editor.selection.getStartElementPath(),
        function(n) {
          return n && n.nodeType == 1 && n.getAttribute("dir");
        }
      );
    },
    doDirectionality = function(range, editor, forward) {
      var bookmark,
        filterFn = function(node) {
          return node.nodeType == 1
            ? !domUtils.isBookmarkNode(node)
            : !domUtils.isWhitespace(node);
        },
        obj = getObj(editor);

      if (obj && range.collapsed) {
        obj.setAttribute("dir", forward);
        return range;
      }
      bookmark = range.createBookmark();
      range.enlarge(true);
      var bookmark2 = range.createBookmark(),
        current = domUtils.getNextDomNode(bookmark2.start, false, filterFn),
        tmpRange = range.cloneRange(),
        tmpNode;
      while (
        current &&
        !(
          domUtils.getPosition(current, bookmark2.end) &
          domUtils.POSITION_FOLLOWING
        )
      ) {
        if (current.nodeType == 3 || !block(current)) {
          tmpRange.setStartBefore(current);
          while (current && current !== bookmark2.end && !block(current)) {
            tmpNode = current;
            current = domUtils.getNextDomNode(current, false, null, function(
              node
            ) {
              return !block(node);
            });
          }
          tmpRange.setEndAfter(tmpNode);
          var common = tmpRange.getCommonAncestor();
          if (!domUtils.isBody(common) && block(common)) {
            //遍历到了block节点
            common.setAttribute("dir", forward);
            current = common;
          } else {
            //没有遍历到，添加一个block节点
            var p = range.document.createElement("p");
            p.setAttribute("dir", forward);
            var frag = tmpRange.extractContents();
            p.appendChild(frag);
            tmpRange.insertNode(p);
            current = p;
          }

          current = domUtils.getNextDomNode(current, false, filterFn);
        } else {
          current = domUtils.getNextDomNode(current, true, filterFn);
        }
      }
      return range.moveToBookmark(bookmark2).moveToBookmark(bookmark);
    };

  /**
     * 文字输入方向
     * @command directionality
     * @method execCommand
     * @param { String } cmdName 命令字符串
     * @param { String } forward 传入'ltr'表示从左向右输入，传入'rtl'表示从右向左输入
     * @example
     * ```javascript
     * editor.execCommand( 'directionality', 'ltr');
     * ```
     */

  /**
     * 查询当前选区的文字输入方向
     * @command directionality
     * @method queryCommandValue
     * @param { String } cmdName 命令字符串
     * @return { String } 返回'ltr'表示从左向右输入，返回'rtl'表示从右向左输入
     * @example
     * ```javascript
     * editor.queryCommandValue( 'directionality');
     * ```
     */
  UE.commands["directionality"] = {
    execCommand: function(cmdName, forward) {
      var range = this.selection.getRange();
      //闭合时单独处理
      if (range.collapsed) {
        var txt = this.document.createTextNode("d");
        range.insertNode(txt);
      }
      doDirectionality(range, this, forward);
      if (txt) {
        range.setStartBefore(txt).collapse(true);
        domUtils.remove(txt);
      }

      range.select();
      return true;
    },
    queryCommandValue: function() {
      var node = getObj(this);
      return node ? node.getAttribute("dir") : "ltr";
    }
  };
})();


// plugins/horizontal.js
/**
 * 插入分割线插件
 * @file
 * @since 1.2.6.1
 */

/**
 * 插入分割线
 * @command horizontal
 * @method execCommand
 * @param { String } cmdName 命令字符串
 * @example
 * ```javascript
 * editor.execCommand( 'horizontal' );
 * ```
 */
UE.plugins["horizontal"] = function() {
  var me = this;
  me.commands["horizontal"] = {
    execCommand: function(cmdName) {
      var me = this;
      if (me.queryCommandState(cmdName) !== -1) {
        me.execCommand("insertHtml", "<hr>");
        var range = me.selection.getRange(),
          start = range.startContainer;
        if (start.nodeType == 1 && !start.childNodes[range.startOffset]) {
          var tmp;
          if ((tmp = start.childNodes[range.startOffset - 1])) {
            if (tmp.nodeType == 1 && tmp.tagName == "HR") {
              if (me.options.enterTag == "p") {
                tmp = me.document.createElement("p");
                range.insertNode(tmp);
                range.setStart(tmp, 0).setCursor();
              } else {
                tmp = me.document.createElement("br");
                range.insertNode(tmp);
                range.setStartBefore(tmp).setCursor();
              }
            }
          }
        }
        return true;
      }
    },
    //边界在table里不能加分隔线
    queryCommandState: function() {
      return domUtils.filterNodeList(
        this.selection.getStartElementPath(),
        "table"
      )
        ? -1
        : 0;
    }
  };
  //    me.addListener('delkeyup',function(){
  //        var rng = this.selection.getRange();
  //        if(browser.ie && browser.version > 8){
  //            rng.txtToElmBoundary(true);
  //            if(domUtils.isStartInblock(rng)){
  //                var tmpNode = rng.startContainer;
  //                var pre = tmpNode.previousSibling;
  //                if(pre && domUtils.isTagNode(pre,'hr')){
  //                    domUtils.remove(pre);
  //                    rng.select();
  //                    return;
  //                }
  //            }
  //        }
  //        if(domUtils.isBody(rng.startContainer)){
  //            var hr = rng.startContainer.childNodes[rng.startOffset -1];
  //            if(hr && hr.nodeName == 'HR'){
  //                var next = hr.nextSibling;
  //                if(next){
  //                    rng.setStart(next,0)
  //                }else if(hr.previousSibling){
  //                    rng.setStartAtLast(hr.previousSibling)
  //                }else{
  //                    var p = this.document.createElement('p');
  //                    hr.parentNode.insertBefore(p,hr);
  //                    domUtils.fillNode(this.document,p);
  //                    rng.setStart(p,0);
  //                }
  //                domUtils.remove(hr);
  //                rng.setCursor(false,true);
  //            }
  //        }
  //    })
  me.addListener("delkeydown", function(name, evt) {
    var rng = this.selection.getRange();
    rng.txtToElmBoundary(true);
    if (domUtils.isStartInblock(rng)) {
      var tmpNode = rng.startContainer;
      var pre = tmpNode.previousSibling;
      if (pre && domUtils.isTagNode(pre, "hr")) {
        domUtils.remove(pre);
        rng.select();
        domUtils.preventDefault(evt);
        return true;
      }
    }
  });
};


// plugins/time.js
/**
 * 插入时间和日期
 * @file
 * @since 1.2.6.1
 */

/**
 * 插入时间，默认格式：12:59:59
 * @command time
 * @method execCommand
 * @param { String } cmd 命令字符串
 * @example
 * ```javascript
 * editor.execCommand( 'time');
 * ```
 */

/**
 * 插入日期，默认格式：2013-08-30
 * @command date
 * @method execCommand
 * @param { String } cmd 命令字符串
 * @example
 * ```javascript
 * editor.execCommand( 'date');
 * ```
 */
UE.commands["time"] = UE.commands["date"] = {
  execCommand: function(cmd, format) {
    var date = new Date();

    function formatTime(date, format) {
      var hh = ("0" + date.getHours()).slice(-2),
        ii = ("0" + date.getMinutes()).slice(-2),
        ss = ("0" + date.getSeconds()).slice(-2);
      format = format || "hh:ii:ss";
      return format.replace(/hh/gi, hh).replace(/ii/gi, ii).replace(/ss/gi, ss);
    }
    function formatDate(date, format) {
      var yyyy = ("000" + date.getFullYear()).slice(-4),
        yy = yyyy.slice(-2),
        mm = ("0" + (date.getMonth() + 1)).slice(-2),
        dd = ("0" + date.getDate()).slice(-2);
      format = format || "yyyy-mm-dd";
      return format
        .replace(/yyyy/gi, yyyy)
        .replace(/yy/gi, yy)
        .replace(/mm/gi, mm)
        .replace(/dd/gi, dd);
    }

    this.execCommand(
      "insertHtml",
      cmd == "time" ? formatTime(date, format) : formatDate(date, format)
    );
  }
};


// plugins/rowspacing.js
/**
 * 段前段后间距插件
 * @file
 * @since 1.2.6.1
 */

/**
 * 设置段间距
 * @command rowspacing
 * @method execCommand
 * @param { String } cmd 命令字符串
 * @param { String } value 段间距的值，以px为单位
 * @param { String } dir 间距位置，top或bottom，分别表示段前和段后
 * @example
 * ```javascript
 * editor.execCommand( 'rowspacing', '10', 'top' );
 * ```
 */

UE.plugins["rowspacing"] = function() {
  var me = this;
  me.setOpt({
    rowspacingtop: ["5", "10", "15", "20", "25"],
    rowspacingbottom: ["5", "10", "15", "20", "25"]
  });
  me.commands["rowspacing"] = {
    execCommand: function(cmdName, value, dir) {
      this.execCommand("paragraph", "p", {
        style: "margin-" + dir + ":" + value + "px"
      });
      return true;
    },
    queryCommandValue: function(cmdName, dir) {
      var pN = domUtils.filterNodeList(
        this.selection.getStartElementPath(),
        function(node) {
          return domUtils.isBlockElm(node);
        }
      ),
        value;
      //trace:1026
      if (pN) {
        value = domUtils
          .getComputedStyle(pN, "margin-" + dir)
          .replace(/[^\d]/g, "");
        return !value ? 0 : value;
      }
      return 0;
    }
  };
};


// plugins/lineheight.js
/**
 * 设置行内间距
 * @file
 * @since 1.2.6.1
 */
UE.plugins["lineheight"] = function() {
  var me = this;
  me.setOpt({ lineheight: ["1", "1.5", "1.75", "2", "3", "4", "5"] });

  /**
     * 行距
     * @command lineheight
     * @method execCommand
     * @param { String } cmdName 命令字符串
     * @param { String } value 传入的行高值， 该值是当前字体的倍数， 例如： 1.5, 1.75
     * @example
     * ```javascript
     * editor.execCommand( 'lineheight', 1.5);
     * ```
     */
  /**
     * 查询当前选区内容的行高大小
     * @command lineheight
     * @method queryCommandValue
     * @param { String } cmd 命令字符串
     * @return { String } 返回当前行高大小
     * @example
     * ```javascript
     * editor.queryCommandValue( 'lineheight' );
     * ```
     */

  me.commands["lineheight"] = {
    execCommand: function(cmdName, value) {
      this.execCommand("paragraph", "p", {
        style: "line-height:" + (value == "1" ? "normal" : value + "em")
      });
      return true;
    },
    queryCommandValue: function() {
      var pN = domUtils.filterNodeList(
        this.selection.getStartElementPath(),
        function(node) {
          return domUtils.isBlockElm(node);
        }
      );
      if (pN) {
        var value = domUtils.getComputedStyle(pN, "line-height");
        return value == "normal" ? 1 : value.replace(/[^\d.]*/gi, "");
      }
    }
  };
};


// plugins/insertcode.js
/**
 * 插入代码插件
 * @file
 * @since 1.2.6.1
 */

UE.plugins["insertcode"] = function() {
  var me = this;
  me.setOpt("insertcode", {
    as3: "ActionScript3",
    bash: "Bash/Shell",
    cpp: "C/C++",
    css: "Css",
    // cf: "CodeFunction",
    "c#": "C#",
    delphi: "Delphi",
    // diff: "Diff",
    erlang: "Erlang",
    groovy: "Groovy",
    html: "Html",
    java: "Java",
    // jfx: "JavaFx",
    js: "Javascript",
    pl: "Perl",
    php: "PHP",
    plain: "Text",
    ps: "PowerShell",
    python: "Python",
    ruby: "Ruby",
    scala: "Scala",
    sql: "SQL",
    vb: "VB",
    xml: "XML",
    mind: "Mind",
  });

  /**
     * 插入代码
     * @command insertcode
     * @method execCommand
     * @param { String } cmd 命令字符串
     * @param { String } lang 插入代码的语言
     * @example
     * ```javascript
     * editor.execCommand( 'insertcode', 'javascript' );
     * ```
     */

  /**
     * 如果选区所在位置是插入插入代码区域，返回代码的语言
     * @command insertcode
     * @method queryCommandValue
     * @param { String } cmd 命令字符串
     * @return { String } 返回代码的语言
     * @example
     * ```javascript
     * editor.queryCommandValue( 'insertcode' );
     * ```
     */

  me.commands["insertcode"] = {
    execCommand: function(cmd, lang) {
      var me = this,
        rng = me.selection.getRange(),
        pre = domUtils.findParentByTagName(rng.startContainer, "pre", true);
      if (pre) {
        pre.className = "brush:" + lang + ";toolbar:false;";
      } else {
        var code = "";
        if (rng.collapsed) {
          code = browser.ie && browser.ie11below
            ? browser.version <= 8 ? "&nbsp;" : ""
            : "<br/>";
        } else {
          var frag = rng.extractContents();
          var div = me.document.createElement("div");
          div.appendChild(frag);

          utils.each(
            UE.filterNode(
              UE.htmlparser(div.innerHTML.replace(/[\r\t]/g, "")),
              me.options.filterTxtRules
            ).children,
            function(node) {
              if (browser.ie && browser.ie11below && browser.version > 8) {
                if (node.type == "element") {
                  if (node.tagName == "br") {
                    code += "\n";
                  } else if (!dtd.$empty[node.tagName]) {
                    utils.each(node.children, function(cn) {
                      if (cn.type == "element") {
                        if (cn.tagName == "br") {
                          code += "\n";
                        } else if (!dtd.$empty[node.tagName]) {
                          code += cn.innerText();
                        }
                      } else {
                        code += cn.data;
                      }
                    });
                    if (!/\n$/.test(code)) {
                      code += "\n";
                    }
                  }
                } else {
                  code += node.data + "\n";
                }
                if (!node.nextSibling() && /\n$/.test(code)) {
                  code = code.replace(/\n$/, "");
                }
              } else {
                if (browser.ie && browser.ie11below) {
                  if (node.type == "element") {
                    if (node.tagName == "br") {
                      code += "<br>";
                    } else if (!dtd.$empty[node.tagName]) {
                      utils.each(node.children, function(cn) {
                        if (cn.type == "element") {
                          if (cn.tagName == "br") {
                            code += "<br>";
                          } else if (!dtd.$empty[node.tagName]) {
                            code += cn.innerText();
                          }
                        } else {
                          code += cn.data;
                        }
                      });
                      if (!/br>$/.test(code)) {
                        code += "<br>";
                      }
                    }
                  } else {
                    code += node.data + "<br>";
                  }
                  if (!node.nextSibling() && /<br>$/.test(code)) {
                    code = code.replace(/<br>$/, "");
                  }
                } else {
                  code += node.type == "element"
                    ? dtd.$empty[node.tagName] ? "" : node.innerText()
                    : node.data;
                  if (!/br\/?\s*>$/.test(code)) {
                    if (!node.nextSibling()) return;
                    code += "<br>";
                  }
                }
              }
            }
          );
        }
        me.execCommand(
          "inserthtml",
          '<pre id="coder"class="brush:' +
            lang +
            ';toolbar:false">' +
            code +
            "</pre>",
          true
        );

        pre = me.document.getElementById("coder");
        domUtils.removeAttributes(pre, "id");
        var tmpNode = pre.previousSibling;

        if (
          tmpNode &&
          ((tmpNode.nodeType == 3 &&
            tmpNode.nodeValue.length == 1 &&
            browser.ie &&
            browser.version == 6) ||
            domUtils.isEmptyBlock(tmpNode))
        ) {
          domUtils.remove(tmpNode);
        }
        var rng = me.selection.getRange();
        if (domUtils.isEmptyBlock(pre)) {
          rng.setStart(pre, 0).setCursor(false, true);
        } else {
          rng.selectNodeContents(pre).select();
        }
      }
    },
    queryCommandValue: function() {
      var path = this.selection.getStartElementPath();
      var lang = "";
      utils.each(path, function(node) {
        if (node.nodeName == "PRE") {
          var match = node.className.match(/brush:([^;]+)/);
          lang = match && match[1] ? match[1] : "";
          return false;
        }
      });
      return lang;
    }
  };

  me.addInputRule(function(root) {
    utils.each(root.getNodesByTagName("pre"), function(pre) {
      var brs = pre.getNodesByTagName("br");
      if (brs.length) {
        browser.ie &&
          browser.ie11below &&
          browser.version > 8 &&
          utils.each(brs, function(br) {
            var txt = UE.uNode.createText("\n");
            br.parentNode.insertBefore(txt, br);
            br.parentNode.removeChild(br);
          });
        return;
      }
      if (browser.ie && browser.ie11below && browser.version > 8) return;
      var code = pre.innerText().split(/\n/);
      pre.innerHTML("");
      utils.each(code, function(c) {
        if (c.length) {
          pre.appendChild(UE.uNode.createText(c));
        }
        pre.appendChild(UE.uNode.createElement("br"));
      });
    });
  });
  me.addOutputRule(function(root) {
    utils.each(root.getNodesByTagName("pre"), function(pre) {
      var code = "";
      utils.each(pre.children, function(n) {
        if (n.type == "text") {
          //在ie下文本内容有可能末尾带有\n要去掉
          //trace:3396
          code += n.data.replace(/[ ]/g, "&nbsp;").replace(/\n$/, "");
        } else {
          if (n.tagName == "br") {
            code += "\n";
          } else {
            code += !dtd.$empty[n.tagName] ? "" : n.innerText();
          }
        }
      });

      pre.innerText(code.replace(/(&nbsp;|\n)+$/, ""));
    });
  });
  //不需要判断highlight的command列表
  me.notNeedCodeQuery = {
    help: 1,
    undo: 1,
    redo: 1,
    source: 1,
    print: 1,
    searchreplace: 1,
    fullscreen: 1,
    preview: 1,
    insertparagraph: 1,
    elementpath: 1,
    insertcode: 1,
    inserthtml: 1,
    selectall: 1
  };
  //将queyCommamndState重置
  var orgQuery = me.queryCommandState;
  me.queryCommandState = function(cmd) {
    var me = this;

    if (
      !me.notNeedCodeQuery[cmd.toLowerCase()] &&
      me.selection &&
      me.queryCommandValue("insertcode")
    ) {
      return -1;
    }
    return UE.Editor.prototype.queryCommandState.apply(this, arguments);
  };
  me.addListener("beforeenterkeydown", function() {
    var rng = me.selection.getRange();
    var pre = domUtils.findParentByTagName(rng.startContainer, "pre", true);
    if (pre) {
      me.fireEvent("saveScene");
      if (!rng.collapsed) {
        rng.deleteContents();
      }
      if (!browser.ie || browser.ie9above) {
        var tmpNode = me.document.createElement("br"),
          pre;
        rng.insertNode(tmpNode).setStartAfter(tmpNode).collapse(true);
        var next = tmpNode.nextSibling;
        if (!next && (!browser.ie || browser.version > 10)) {
          rng.insertNode(tmpNode.cloneNode(false));
        } else {
          rng.setStartAfter(tmpNode);
        }
        pre = tmpNode.previousSibling;
        var tmp;
        while (pre) {
          tmp = pre;
          pre = pre.previousSibling;
          if (!pre || pre.nodeName == "BR") {
            pre = tmp;
            break;
          }
        }
        if (pre) {
          var str = "";
          while (
            pre &&
            pre.nodeName != "BR" &&
            new RegExp("^[\\s" + domUtils.fillChar + "]*$").test(pre.nodeValue)
          ) {
            str += pre.nodeValue;
            pre = pre.nextSibling;
          }
          if (pre.nodeName != "BR") {
            var match = pre.nodeValue.match(
              new RegExp("^([\\s" + domUtils.fillChar + "]+)")
            );
            if (match && match[1]) {
              str += match[1];
            }
          }
          if (str) {
            str = me.document.createTextNode(str);
            rng.insertNode(str).setStartAfter(str);
          }
        }
        rng.collapse(true).select(true);
      } else {
        if (browser.version > 8) {
          var txt = me.document.createTextNode("\n");
          var start = rng.startContainer;
          if (rng.startOffset == 0) {
            var preNode = start.previousSibling;
            if (preNode) {
              rng.insertNode(txt);
              var fillchar = me.document.createTextNode(" ");
              rng
                .setStartAfter(txt)
                .insertNode(fillchar)
                .setStart(fillchar, 0)
                .collapse(true)
                .select(true);
            }
          } else {
            rng.insertNode(txt).setStartAfter(txt);
            var fillchar = me.document.createTextNode(" ");
            start = rng.startContainer.childNodes[rng.startOffset];
            if (start && !/^\n/.test(start.nodeValue)) {
              rng.setStartBefore(txt);
            }
            rng
              .insertNode(fillchar)
              .setStart(fillchar, 0)
              .collapse(true)
              .select(true);
          }
        } else {
          var tmpNode = me.document.createElement("br");
          rng.insertNode(tmpNode);
          rng.insertNode(me.document.createTextNode(domUtils.fillChar));
          rng.setStartAfter(tmpNode);
          pre = tmpNode.previousSibling;
          var tmp;
          while (pre) {
            tmp = pre;
            pre = pre.previousSibling;
            if (!pre || pre.nodeName == "BR") {
              pre = tmp;
              break;
            }
          }
          if (pre) {
            var str = "";
            while (
              pre &&
              pre.nodeName != "BR" &&
              new RegExp("^[ " + domUtils.fillChar + "]*$").test(pre.nodeValue)
            ) {
              str += pre.nodeValue;
              pre = pre.nextSibling;
            }
            if (pre.nodeName != "BR") {
              var match = pre.nodeValue.match(
                new RegExp("^([ " + domUtils.fillChar + "]+)")
              );
              if (match && match[1]) {
                str += match[1];
              }
            }

            str = me.document.createTextNode(str);
            rng.insertNode(str).setStartAfter(str);
          }
          rng.collapse(true).select();
        }
      }
      me.fireEvent("saveScene");
      return true;
    }
  });

  me.addListener("tabkeydown", function(cmd, evt) {
    var rng = me.selection.getRange();
    var pre = domUtils.findParentByTagName(rng.startContainer, "pre", true);
    if (pre) {
      me.fireEvent("saveScene");
      if (evt.shiftKey) {
      } else {
        if (!rng.collapsed) {
          var bk = rng.createBookmark();
          var start = bk.start.previousSibling;

          while (start) {
            if (pre.firstChild === start && !domUtils.isBr(start)) {
              pre.insertBefore(me.document.createTextNode("    "), start);

              break;
            }
            if (domUtils.isBr(start)) {
              pre.insertBefore(
                me.document.createTextNode("    "),
                start.nextSibling
              );

              break;
            }
            start = start.previousSibling;
          }
          var end = bk.end;
          start = bk.start.nextSibling;
          if (pre.firstChild === bk.start) {
            pre.insertBefore(
              me.document.createTextNode("    "),
              start.nextSibling
            );
          }
          while (start && start !== end) {
            if (domUtils.isBr(start) && start.nextSibling) {
              if (start.nextSibling === end) {
                break;
              }
              pre.insertBefore(
                me.document.createTextNode("    "),
                start.nextSibling
              );
            }

            start = start.nextSibling;
          }
          rng.moveToBookmark(bk).select();
        } else {
          var tmpNode = me.document.createTextNode("    ");
          rng
            .insertNode(tmpNode)
            .setStartAfter(tmpNode)
            .collapse(true)
            .select(true);
        }
      }

      me.fireEvent("saveScene");
      return true;
    }
  });

  me.addListener("beforeinserthtml", function(evtName, html) {
    var me = this,
      rng = me.selection.getRange(),
      pre = domUtils.findParentByTagName(rng.startContainer, "pre", true);
    if (pre) {
      if (!rng.collapsed) {
        rng.deleteContents();
      }
      var htmlstr = "";
      if (browser.ie && browser.version > 8) {
        utils.each(
          UE.filterNode(UE.htmlparser(html), me.options.filterTxtRules)
            .children,
          function(node) {
            if (node.type == "element") {
              if (node.tagName == "br") {
                htmlstr += "\n";
              } else if (!dtd.$empty[node.tagName]) {
                utils.each(node.children, function(cn) {
                  if (cn.type == "element") {
                    if (cn.tagName == "br") {
                      htmlstr += "\n";
                    } else if (!dtd.$empty[node.tagName]) {
                      htmlstr += cn.innerText();
                    }
                  } else {
                    htmlstr += cn.data;
                  }
                });
                if (!/\n$/.test(htmlstr)) {
                  htmlstr += "\n";
                }
              }
            } else {
              htmlstr += node.data + "\n";
            }
            if (!node.nextSibling() && /\n$/.test(htmlstr)) {
              htmlstr = htmlstr.replace(/\n$/, "");
            }
          }
        );
        var tmpNode = me.document.createTextNode(
          utils.html(htmlstr.replace(/&nbsp;/g, " "))
        );
        rng.insertNode(tmpNode).selectNode(tmpNode).select();
      } else {
        var frag = me.document.createDocumentFragment();

        utils.each(
          UE.filterNode(UE.htmlparser(html), me.options.filterTxtRules)
            .children,
          function(node) {
            if (node.type == "element") {
              if (node.tagName == "br") {
                frag.appendChild(me.document.createElement("br"));
              } else if (!dtd.$empty[node.tagName]) {
                utils.each(node.children, function(cn) {
                  if (cn.type == "element") {
                    if (cn.tagName == "br") {
                      frag.appendChild(me.document.createElement("br"));
                    } else if (!dtd.$empty[node.tagName]) {
                      frag.appendChild(
                        me.document.createTextNode(
                          utils.html(cn.innerText().replace(/&nbsp;/g, " "))
                        )
                      );
                    }
                  } else {
                    frag.appendChild(
                      me.document.createTextNode(
                        utils.html(cn.data.replace(/&nbsp;/g, " "))
                      )
                    );
                  }
                });
                if (frag.lastChild.nodeName != "BR") {
                  frag.appendChild(me.document.createElement("br"));
                }
              }
            } else {
              frag.appendChild(
                me.document.createTextNode(
                  utils.html(node.data.replace(/&nbsp;/g, " "))
                )
              );
            }
            if (!node.nextSibling() && frag.lastChild.nodeName == "BR") {
              frag.removeChild(frag.lastChild);
            }
          }
        );
        rng.insertNode(frag).select();
      }

      return true;
    }
  });
  //方向键的处理
  me.addListener("keydown", function(cmd, evt) {
    var me = this,
      keyCode = evt.keyCode || evt.which;
    if (keyCode == 40) {
      var rng = me.selection.getRange(),
        pre,
        start = rng.startContainer;
      if (
        rng.collapsed &&
        (pre = domUtils.findParentByTagName(rng.startContainer, "pre", true)) &&
        !pre.nextSibling
      ) {
        var last = pre.lastChild;
        while (last && last.nodeName == "BR") {
          last = last.previousSibling;
        }
        if (
          last === start ||
          (rng.startContainer === pre &&
            rng.startOffset == pre.childNodes.length)
        ) {
          me.execCommand("insertparagraph");
          domUtils.preventDefault(evt);
        }
      }
    }
  });
  //trace:3395
  me.addListener("delkeydown", function(type, evt) {
    var rng = this.selection.getRange();
    rng.txtToElmBoundary(true);
    var start = rng.startContainer;
    if (
      domUtils.isTagNode(start, "pre") &&
      rng.collapsed &&
      domUtils.isStartInblock(rng)
    ) {
      var p = me.document.createElement("p");
      domUtils.fillNode(me.document, p);
      start.parentNode.insertBefore(p, start);
      domUtils.remove(start);
      rng.setStart(p, 0).setCursor(false, true);
      domUtils.preventDefault(evt);
      return true;
    }
  });
};


// plugins/cleardoc.js
/**
 * 清空文档插件
 * @file
 * @since 1.2.6.1
 */

/**
 * 清空文档
 * @command cleardoc
 * @method execCommand
 * @param { String } cmd 命令字符串
 * @example
 * ```javascript
 * //editor 是编辑器实例
 * editor.execCommand('cleardoc');
 * ```
 */

UE.commands["cleardoc"] = {
  execCommand: function(cmdName) {
    var me = this,
      enterTag = me.options.enterTag,
      range = me.selection.getRange();
    if (enterTag == "br") {
      me.body.innerHTML = "<br/>";
      range.setStart(me.body, 0).setCursor();
    } else {
      me.body.innerHTML = "<p>" + (ie ? "" : "<br/>") + "</p>";
      range.setStart(me.body.firstChild, 0).setCursor(false, true);
    }
    setTimeout(function() {
      me.fireEvent("clearDoc");
    }, 0);
  }
};


// plugins/anchor.js
/**
 * 锚点插件，为UEditor提供插入锚点支持
 * @file
 * @since 1.2.6.1
 */
UE.plugin.register("anchor", function() {
  return {
    bindEvents: {
      ready: function() {
        utils.cssRule(
          "anchor",
          ".anchorclass{background: url('" +
            this.options.themePath +
            this.options.theme +
            "/images/anchor.gif') no-repeat scroll left center transparent;cursor: auto;display: inline-block;height: 16px;width: 15px;}",
          this.document
        );
      }
    },
    outputRule: function(root) {
      utils.each(root.getNodesByTagName("img"), function(a) {
        var val;
        if ((val = a.getAttr("anchorname"))) {
          a.tagName = "a";
          a.setAttr({
            anchorname: "",
            name: val,
            class: ""
          });
        }
      });
    },
    inputRule: function(root) {
      utils.each(root.getNodesByTagName("a"), function(a) {
        var val;
        if ((val = a.getAttr("name")) && !a.getAttr("href")) {
          //过滤掉word冗余标签
          //_Toc\d+有可能勿命中
          if (/^\_Toc\d+$/.test(val)) {
            a.parentNode.removeChild(a);
            return;
          }
          a.tagName = "img";
          a.setAttr({
            anchorname: a.getAttr("name"),
            class: "anchorclass"
          });
          a.setAttr("name");
        }
      });
    },
    commands: {
      /**
            * 插入锚点
            * @command anchor
            * @method execCommand
            * @param { String } cmd 命令字符串
            * @param { String } name 锚点名称字符串
            * @example
            * ```javascript
            * //editor 是编辑器实例
            * editor.execCommand('anchor', 'anchor1');
            * ```
            */
      anchor: {
        execCommand: function(cmd, name) {
          var range = this.selection.getRange(),
            img = range.getClosedNode();
          if (img && img.getAttribute("anchorname")) {
            if (name) {
              img.setAttribute("anchorname", name);
            } else {
              range.setStartBefore(img).setCursor();
              domUtils.remove(img);
            }
          } else {
            if (name) {
              //只在选区的开始插入
              var anchor = this.document.createElement("img");
              range.collapse(true);
              domUtils.setAttributes(anchor, {
                anchorname: name,
                class: "anchorclass"
              });
              range
                .insertNode(anchor)
                .setStartAfter(anchor)
                .setCursor(false, true);
            }
          }
        }
      }
    }
  };
});


// plugins/wordcount.js
///import core
///commands 字数统计
///commandsName  WordCount,wordCount
///commandsTitle  字数统计
/*
 * Created by JetBrains WebStorm.
 * User: taoqili
 * Date: 11-9-7
 * Time: 下午8:18
 * To change this template use File | Settings | File Templates.
 */

UE.plugins["wordcount"] = function() {
  var me = this;
  me.setOpt("wordCount", true);
  me.addListener("contentchange", function() {
    me.fireEvent("wordcount");
  });
  var timer;
  me.addListener("ready", function() {
    var me = this;
    domUtils.on(me.body, "keyup", function(evt) {
      var code = evt.keyCode || evt.which,
        //忽略的按键,ctr,alt,shift,方向键
        ignores = {
          "16": 1,
          "18": 1,
          "20": 1,
          "37": 1,
          "38": 1,
          "39": 1,
          "40": 1
        };
      if (code in ignores) return;
      clearTimeout(timer);
      timer = setTimeout(function() {
        me.fireEvent("wordcount");
      }, 200);
    });
  });
};


// plugins/pagebreak.js
/**
 * 分页功能插件
 * @file
 * @since 1.2.6.1
 */
UE.plugins["pagebreak"] = function() {
  var me = this,
    notBreakTags = ["td"];
  me.setOpt("pageBreakTag", "_ueditor_page_break_tag_");

  function fillNode(node) {
    if (domUtils.isEmptyBlock(node)) {
      var firstChild = node.firstChild,
        tmpNode;

      while (
        firstChild &&
        firstChild.nodeType == 1 &&
        domUtils.isEmptyBlock(firstChild)
      ) {
        tmpNode = firstChild;
        firstChild = firstChild.firstChild;
      }
      !tmpNode && (tmpNode = node);
      domUtils.fillNode(me.document, tmpNode);
    }
  }
  //分页符样式添加

  me.ready(function() {
    utils.cssRule(
      "pagebreak",
      ".pagebreak{display:block;clear:both !important;cursor:default !important;width: 100% !important;margin:0;}",
      me.document
    );
  });
  function isHr(node) {
    return (
      node &&
      node.nodeType == 1 &&
      node.tagName == "HR" &&
      node.className == "pagebreak"
    );
  }
  me.addInputRule(function(root) {
    root.traversal(function(node) {
      if (node.type == "text" && node.data == me.options.pageBreakTag) {
        var hr = UE.uNode.createElement(
          '<hr class="pagebreak" noshade="noshade" size="5" style="-webkit-user-select: none;">'
        );
        node.parentNode.insertBefore(hr, node);
        node.parentNode.removeChild(node);
      }
    });
  });
  me.addOutputRule(function(node) {
    utils.each(node.getNodesByTagName("hr"), function(n) {
      if (n.getAttr("class") == "pagebreak") {
        var txt = UE.uNode.createText(me.options.pageBreakTag);
        n.parentNode.insertBefore(txt, n);
        n.parentNode.removeChild(n);
      }
    });
  });

  /**
     * 插入分页符
     * @command pagebreak
     * @method execCommand
     * @param { String } cmd 命令字符串
     * @remind 在表格中插入分页符会把表格切分成两部分
     * @remind 获取编辑器内的数据时， 编辑器会把分页符转换成“_ueditor_page_break_tag_”字符串，
     *          以便于提交数据到服务器端后处理分页。
     * @example
     * ```javascript
     * editor.execCommand( 'pagebreak'); //插入一个hr标签，带有样式类名pagebreak
     * ```
     */

  me.commands["pagebreak"] = {
    execCommand: function() {
      var range = me.selection.getRange(),
        hr = me.document.createElement("hr");
      domUtils.setAttributes(hr, {
        class: "pagebreak",
        noshade: "noshade",
        size: "5"
      });
      domUtils.unSelectable(hr);
      //table单独处理
      var node = domUtils.findParentByTagName(
        range.startContainer,
        notBreakTags,
        true
      ),
        parents = [],
        pN;
      if (node) {
        switch (node.tagName) {
          case "TD":
            pN = node.parentNode;
            if (!pN.previousSibling) {
              var table = domUtils.findParentByTagName(pN, "table");
              //                            var tableWrapDiv = table.parentNode;
              //                            if(tableWrapDiv && tableWrapDiv.nodeType == 1
              //                                && tableWrapDiv.tagName == 'DIV'
              //                                && tableWrapDiv.getAttribute('dropdrag')
              //                                ){
              //                                domUtils.remove(tableWrapDiv,true);
              //                            }
              table.parentNode.insertBefore(hr, table);
              parents = domUtils.findParents(hr, true);
            } else {
              pN.parentNode.insertBefore(hr, pN);
              parents = domUtils.findParents(hr);
            }
            pN = parents[1];
            if (hr !== pN) {
              domUtils.breakParent(hr, pN);
            }
            //table要重写绑定一下拖拽
            me.fireEvent("afteradjusttable", me.document);
        }
      } else {
        if (!range.collapsed) {
          range.deleteContents();
          var start = range.startContainer;
          while (
            !domUtils.isBody(start) &&
            domUtils.isBlockElm(start) &&
            domUtils.isEmptyNode(start)
          ) {
            range.setStartBefore(start).collapse(true);
            domUtils.remove(start);
            start = range.startContainer;
          }
        }
        range.insertNode(hr);

        var pN = hr.parentNode,
          nextNode;
        while (!domUtils.isBody(pN)) {
          domUtils.breakParent(hr, pN);
          nextNode = hr.nextSibling;
          if (nextNode && domUtils.isEmptyBlock(nextNode)) {
            domUtils.remove(nextNode);
          }
          pN = hr.parentNode;
        }
        nextNode = hr.nextSibling;
        var pre = hr.previousSibling;
        if (isHr(pre)) {
          domUtils.remove(pre);
        } else {
          pre && fillNode(pre);
        }

        if (!nextNode) {
          var p = me.document.createElement("p");

          hr.parentNode.appendChild(p);
          domUtils.fillNode(me.document, p);
          range.setStart(p, 0).collapse(true);
        } else {
          if (isHr(nextNode)) {
            domUtils.remove(nextNode);
          } else {
            fillNode(nextNode);
          }
          range.setEndAfter(hr).collapse(false);
        }

        range.select(true);
      }
    }
  };
};


// plugins/wordimage.js
///import core
///commands 本地图片引导上传
///commandsName  WordImage
///commandsTitle  本地图片引导上传
///commandsDialog  dialogs\wordimage

UE.plugin.register("wordimage", function() {
  var me = this,
    images = [];

  this.addListener("click", function (type, evt) {
    var el = evt.target || evt.srcElement;
    if ('IMG' == el.tagName && el.getAttribute('data-word-image')) {
      me.ui._dialogs.wordimageDialog && me.ui._dialogs.wordimageDialog.open();
    }
  });

  return {
    commands: {
      wordimage: {
        execCommand: function() {
          var images = domUtils.getElementsByTagName(me.body, "img");
          var urlList = [];
          for (var i = 0, ci; (ci = images[i++]); ) {
            var url = ci.getAttribute("data-word-image");
            url && urlList.push(url);
          }
          return urlList;
        },
        queryCommandState: function() {
          images = domUtils.getElementsByTagName(me.body, "img");
          for (var i = 0, ci; (ci = images[i++]); ) {
            if (ci.getAttribute("data-word-image")) {
              return 1;
            }
          }
          return -1;
        },
        notNeedUndo: true
      }
    },
    inputRule: function(root) {
      utils.each(root.getNodesByTagName("img"), function(img) {
        var attrs = img.attrs,
          flag = parseInt(attrs.width) < 128 || parseInt(attrs.height) < 43,
          opt = me.options,
          src = opt.UEDITOR_HOME_URL + "themes/default/images/spacer.gif";
        if (attrs["src"] && /^(?:(file:\/+))/.test(attrs["src"])) {
          img.setAttr({
            width: attrs.width,
            height: attrs.height,
            alt: attrs.alt,
            'data-word-image': attrs.src,
            src: src,
            style:
              "background:url(" +
                (flag
                  ? opt.themePath + opt.theme + "/images/word.gif"
                  : opt.langPath + opt.lang + "/images/localimage.png") +
                ") no-repeat center center;border:1px solid #ddd"
          });
        }
      });
    }
  };
});


// plugins/autosave.js
UE.plugin.register("autosave", function () {
    var me = this, saveKey = null;

    function save(editor) {
        var saveData;

        if (!editor.hasContents()) {
            //这里不能调用命令来删除， 会造成事件死循环
            saveKey && me.removePreferences(saveKey);
            return;
        }

        editor._autoSaveTimer = null;

        saveData = me.body.innerHTML;

        if (
            editor.fireEvent("beforeautosave", {
                content: saveData
            }) === false
        ) {
            return;
        }

        // console.log('autosave', saveKey, saveData);
        me.setPreferences(saveKey, saveData);

        editor.fireEvent("afterautosave", {
            content: saveData
        });
    }

    return {
        defaultOptions: {
            autoSaveEnable: true,
            autoSaveRestore: false,
            autoSaveKey: null,
        },
        bindEvents: {
            ready: function () {
                saveKey = me.getOpt('autoSaveKey');
                if (!saveKey) {
                    var _suffix = "_DraftsData", key = null;

                    if (me.key) {
                        key = me.key + _suffix;
                    } else {
                        key = (me.container.parentNode.id || "ue-common") + _suffix;
                    }
                    saveKey = (location.protocol + location.host + location.pathname).replace(
                        /[.:\/]/g,
                        "_"
                    ) + key;
                }
                if (me.getOpt('autoSaveRestore')) {
                    var data = me.getPreferences(saveKey);
                    // console.log('saveKey', saveKey, data);
                    if (data) {
                        me.body.innerHTML = data;
                        me.fireEvent('showmessage',{
                          type:'info',
                          content:me.getLang('autosave').autoRestoreTip
                        })
                    }
                }
                // console.log('saveKey', saveKey);
            },
            beforesubmit: function(){
                if (!me.getOpt("autoSaveEnable") || !saveKey) {
                  return;
                }
                me.execCommand('clear_auto_save_content');
            },
            contentchange: function () {
                if(!me.isReady){
                    return;
                }
                if (!me.getOpt("autoSaveEnable") || !saveKey) {
                    return;
                }

                if (me._autoSaveTimer) {
                    window.clearTimeout(me._autoSaveTimer);
                }

                me._autoSaveTimer = window.setTimeout(function () {
                    save(me);
                }, 1000);
            }
        },
        commands: {
            clear_auto_save_content: {
                execCommand: function (cmd, name) {
                    if (saveKey && me.getPreferences(saveKey)) {
                        me.removePreferences(saveKey);
                    }
                },
                notNeedUndo: true,
                ignoreContentChange: true
            },

            set_auto_save_content: {
                execCommand: function (cmd, name) {
                    save(me);
                },
                notNeedUndo: true,
                ignoreContentChange: true
            },

            get_auto_save_content: {
                execCommand: function (cmd, name) {
                    return me.getPreferences(saveKey) || "";
                },
                notNeedUndo: true,
                ignoreContentChange: true
            },

            auto_save_restore: {
                execCommand: function (cmd, name) {
                    if (saveKey) {
                        me.body.innerHTML =
                            me.getPreferences(saveKey) || "<p>" + domUtils.fillHtml + "</p>";
                        me.focus(true);
                    }
                },
                queryCommandState: function () {
                    return saveKey ? (me.getPreferences(saveKey) === null ? -1 : 0) : -1;
                },
                notNeedUndo: true,
                ignoreContentChange: true
            }
        }
    };
});


// plugins/formula.js
UE.plugin.register("formula", function () {
  var me = this, images = [];

  return {
    commands: {
      formula: {
        execCommand: function (cmdName, value) {
          var range = me.selection.getRange(),
            img = range.getClosedNode();

          value = encodeURIComponent(value);
          var formulaConfig = me.getOpt('formulaConfig');
          var src = formulaConfig.imageUrlTemplate.replace(/\{\}/, value);

          if (img) {
            img.setAttribute("src", src);
          } else {
            me.execCommand("insertHtml", '<img src="' + src + '" data-formula-image="' + value + '" />');
          }
        },
      }
    },
  };
});


// plugins/dragdrop.js
UE.plugins["dragdrop"] = function() {
  var me = this;
  me.ready(function() {
    domUtils.on(this.body, "dragend", function() {
      var rng = me.selection.getRange();
      var node = rng.getClosedNode() || me.selection.getStart();

      if (node && node.tagName == "IMG") {
        var pre = node.previousSibling,
          next;
        while ((next = node.nextSibling)) {
          if (
            next.nodeType == 1 &&
            next.tagName == "SPAN" &&
            !next.firstChild
          ) {
            domUtils.remove(next);
          } else {
            break;
          }
        }

        if (
          ((pre && pre.nodeType == 1 && !domUtils.isEmptyBlock(pre)) || !pre) &&
          (!next || (next && !domUtils.isEmptyBlock(next)))
        ) {
          if (pre && pre.tagName == "P" && !domUtils.isEmptyBlock(pre)) {
            pre.appendChild(node);
            domUtils.moveChild(next, pre);
            domUtils.remove(next);
          } else if (
            next &&
            next.tagName == "P" &&
            !domUtils.isEmptyBlock(next)
          ) {
            next.insertBefore(node, next.firstChild);
          }

          if (pre && pre.tagName == "P" && domUtils.isEmptyBlock(pre)) {
            domUtils.remove(pre);
          }
          if (next && next.tagName == "P" && domUtils.isEmptyBlock(next)) {
            domUtils.remove(next);
          }
          rng.selectNode(node).select();
          me.fireEvent("saveScene");
        }
      }
    });
  });
  me.addListener("keyup", function(type, evt) {
    var keyCode = evt.keyCode || evt.which;
    if (keyCode == 13) {
      var rng = me.selection.getRange(),
        node;
      if (
        (node = domUtils.findParentByTagName(rng.startContainer, "p", true))
      ) {
        if (domUtils.getComputedStyle(node, "text-align") == "center") {
          domUtils.removeStyle(node, "text-align");
        }
      }
    }
  });
};


// plugins/undo.js
/**
 * undo redo
 * @file
 * @since 1.2.6.1
 */

/**
 * 撤销上一次执行的命令
 * @command undo
 * @method execCommand
 * @param { String } cmd 命令字符串
 * @example
 * ```javascript
 * editor.execCommand( 'undo' );
 * ```
 */

/**
 * 重做上一次执行的命令
 * @command redo
 * @method execCommand
 * @param { String } cmd 命令字符串
 * @example
 * ```javascript
 * editor.execCommand( 'redo' );
 * ```
 */

UE.plugins["undo"] = function() {
  var saveSceneTimer;
  var me = this,
    maxUndoCount = me.options.maxUndoCount || 20,
    maxInputCount = me.options.maxInputCount || 20,
    fillchar = new RegExp(domUtils.fillChar + "|</hr>", "gi"); // ie会产生多余的</hr>
  var noNeedFillCharTags = {
    ol: 1,
    ul: 1,
    table: 1,
    tbody: 1,
    tr: 1,
    body: 1
  };
  var orgState = me.options.autoClearEmptyNode;
  function compareAddr(indexA, indexB) {
    if (indexA.length != indexB.length) return 0;
    for (var i = 0, l = indexA.length; i < l; i++) {
      if (indexA[i] != indexB[i]) return 0;
    }
    return 1;
  }

  function compareRangeAddress(rngAddrA, rngAddrB) {
    if (rngAddrA.collapsed != rngAddrB.collapsed) {
      return 0;
    }
    if (
      !compareAddr(rngAddrA.startAddress, rngAddrB.startAddress) ||
      !compareAddr(rngAddrA.endAddress, rngAddrB.endAddress)
    ) {
      return 0;
    }
    return 1;
  }

  function UndoManager() {
    this.list = [];
    this.index = 0;
    this.hasUndo = false;
    this.hasRedo = false;
    this.undo = function() {
      if (this.hasUndo) {
        if (!this.list[this.index - 1] && this.list.length == 1) {
          this.reset();
          return;
        }
        while (
          this.list[this.index].content == this.list[this.index - 1].content
        ) {
          this.index--;
          if (this.index == 0) {
            return this.restore(0);
          }
        }
        this.restore(--this.index);
      }
    };
    this.redo = function() {
      if (this.hasRedo) {
        while (
          this.list[this.index].content == this.list[this.index + 1].content
        ) {
          this.index++;
          if (this.index == this.list.length - 1) {
            return this.restore(this.index);
          }
        }
        this.restore(++this.index);
      }
    };

    this.restore = function() {
      var me = this.editor;
      var scene = this.list[this.index];
      var root = UE.htmlparser(scene.content.replace(fillchar, ""));
      me.options.autoClearEmptyNode = false;
      me.filterInputRule(root);
      me.options.autoClearEmptyNode = orgState;
      //trace:873
      //去掉展位符
      me.document.body.innerHTML = root.toHtml();
      me.fireEvent("afterscencerestore");
      //处理undo后空格不展位的问题
      if (browser.ie) {
        utils.each(
          domUtils.getElementsByTagName(me.document, "td th caption p"),
          function(node) {
            if (domUtils.isEmptyNode(node)) {
              domUtils.fillNode(me.document, node);
            }
          }
        );
      }

      try {
        var rng = new dom.Range(me.document).moveToAddress(scene.address);
        rng.select(
          noNeedFillCharTags[rng.startContainer.nodeName.toLowerCase()]
        );
      } catch (e) {}

      this.update();
      this.clearKey();
      //不能把自己reset了
      me.fireEvent("reset", true);
    };

    this.getScene = function() {
      var me = this.editor;
      var rng = me.selection.getRange(),
        rngAddress = rng.createAddress(false, true);
      me.fireEvent("beforegetscene");
      var root = UE.htmlparser(me.body.innerHTML);
      me.options.autoClearEmptyNode = false;
      me.filterOutputRule(root);
      me.options.autoClearEmptyNode = orgState;
      var cont = root.toHtml();
      //trace:3461
      //这个会引起回退时导致空格丢失的情况
      //            browser.ie && (cont = cont.replace(/>&nbsp;</g, '><').replace(/\s*</g, '<').replace(/>\s*/g, '>'));
      me.fireEvent("aftergetscene");

      return {
        address: rngAddress,
        content: cont
      };
    };
    this.save = function(notCompareRange, notSetCursor) {

      clearTimeout(saveSceneTimer);
      var currentScene = this.getScene(notSetCursor),
        lastScene = this.list[this.index];
      if (lastScene && lastScene.content != currentScene.content) {
        me.trigger("contentchange");
      }
      //内容相同位置相同不存
      if (
        lastScene &&
        lastScene.content == currentScene.content &&
        (notCompareRange
          ? 1
          : compareRangeAddress(lastScene.address, currentScene.address))
      ) {
        return;
      }
      this.list = this.list.slice(0, this.index + 1);
      this.list.push(currentScene);
      //如果大于最大数量了，就把最前的剔除
      if (this.list.length > maxUndoCount) {
        this.list.shift();
      }
      this.index = this.list.length - 1;
      this.clearKey();
      //跟新undo/redo状态
      this.update();
    };
    this.update = function() {
      this.hasRedo = !!this.list[this.index + 1];
      this.hasUndo = !!this.list[this.index - 1];
    };
    this.reset = function() {
      this.list = [];
      this.index = 0;
      this.hasUndo = false;
      this.hasRedo = false;
      this.clearKey();
    };
    this.clearKey = function() {
      keycont = 0;
      lastKeyCode = null;
    };
  }

  me.undoManger = new UndoManager();
  me.undoManger.editor = me;
  function saveScene() {
    this.undoManger.save();
  }

  me.addListener("saveScene", function() {
    var args = Array.prototype.splice.call(arguments, 1);
    this.undoManger.save.apply(this.undoManger, args);
  });

  //    me.addListener('beforeexeccommand', saveScene);
  //    me.addListener('afterexeccommand', saveScene);

  me.addListener("reset", function(type, exclude) {
    if (!exclude) {
      this.undoManger.reset();
    }
  });
  me.commands["redo"] = me.commands["undo"] = {
    execCommand: function(cmdName) {
      this.undoManger[cmdName]();
    },
    queryCommandState: function(cmdName) {
      return this.undoManger[
        "has" + (cmdName.toLowerCase() == "undo" ? "Undo" : "Redo")
      ]
        ? 0
        : -1;
    },
    notNeedUndo: 1
  };

  var keys = {
    //  /*Backspace*/ 8:1, /*Delete*/ 46:1,
    /*Shift*/ 16: 1,
    /*Ctrl*/ 17: 1,
    /*Alt*/ 18: 1,
    37: 1,
    38: 1,
    39: 1,
    40: 1
  },
    keycont = 0,
    lastKeyCode;
  //输入法状态下不计算字符数
  var inputType = false;
  me.addListener("ready", function() {
    domUtils.on(this.body, "compositionstart", function() {
      inputType = true;
    });
    domUtils.on(this.body, "compositionend", function() {
      inputType = false;
    });
  });
  //快捷键
  me.addshortcutkey({
    Undo: "ctrl+90", //undo
    Redo: "ctrl+89" //redo
  });
  var isCollapsed = true;
  me.addListener("keydown", function(type, evt) {
    var me = this;
    var keyCode = evt.keyCode || evt.which;
    if (
      !keys[keyCode] &&
      !evt.ctrlKey &&
      !evt.metaKey &&
      !evt.shiftKey &&
      !evt.altKey
    ) {
      if (inputType) return;

      if (!me.selection.getRange().collapsed) {
        me.undoManger.save(false, true);
        isCollapsed = false;
        return;
      }
      if (me.undoManger.list.length == 0) {
        me.undoManger.save(true);
      }
      clearTimeout(saveSceneTimer);
      function save(cont) {
        cont.undoManger.save(false, true);
        cont.fireEvent("selectionchange");
      }
      saveSceneTimer = setTimeout(function() {
        if (inputType) {
          var interalTimer = setInterval(function() {
            if (!inputType) {
              save(me);
              clearInterval(interalTimer);
            }
          }, 300);
          return;
        }
        save(me);
      }, 200);

      lastKeyCode = keyCode;
      keycont++;
      if (keycont >= maxInputCount) {
        save(me);
      }
    }
  });
  me.addListener("keyup", function(type, evt) {
    var keyCode = evt.keyCode || evt.which;
    if (
      !keys[keyCode] &&
      !evt.ctrlKey &&
      !evt.metaKey &&
      !evt.shiftKey &&
      !evt.altKey
    ) {
      if (inputType) return;
      if (!isCollapsed) {
        this.undoManger.save(false, true);
        isCollapsed = true;
      }
    }
  });
  //扩展实例，添加关闭和开启命令undo
  me.stopCmdUndo = function() {
    me.__hasEnterExecCommand = true;
  };
  me.startCmdUndo = function() {
    me.__hasEnterExecCommand = false;
  };
};


// plugins/copy.js
UE.plugin.register("copy", function() {
  var me = this;

  function initZeroClipboard() {
    ZeroClipboard.config({
      debug: false,
      swfPath:
        me.options.UEDITOR_HOME_URL +
          "third-party/zeroclipboard/ZeroClipboard.swf"
    });

    var client = (me.zeroclipboard = new ZeroClipboard());

    // 复制内容
    client.on("copy", function(e) {
      var client = e.client,
        rng = me.selection.getRange(),
        div = document.createElement("div");

      div.appendChild(rng.cloneContents());
      client.setText(div.innerText || div.textContent);
      client.setHtml(div.innerHTML);
      rng.select();
    });
    // hover事件传递到target
    client.on("mouseover mouseout", function(e) {
      var target = e.target;
      if (target) {
        if (e.type == "mouseover") {
          domUtils.addClass(target, "edui-state-hover");
        } else if (e.type == "mouseout") {
          domUtils.removeClasses(target, "edui-state-hover");
        }
      }
    });
    // flash加载不成功
    client.on("wrongflash noflash", function() {
      ZeroClipboard.destroy();
    });

    // 触发事件
    me.fireEvent("zeroclipboardready", client);
  }

  return {
    bindEvents: {
      ready: function() {
        if (!browser.ie) {
          if (window.ZeroClipboard) {
            initZeroClipboard();
          } else {
            utils.loadFile(
              document,
              {
                src:
                  me.options.UEDITOR_HOME_URL +
                    "third-party/zeroclipboard/ZeroClipboard.js",
                tag: "script",
                type: "text/javascript",
                defer: "defer"
              },
              function() {
                initZeroClipboard();
              }
            );
          }
        }
      }
    },
    commands: {
      copy: {
        execCommand: function(cmd) {
          if (!me.document.execCommand("copy")) {
            alert(me.getLang("copymsg"));
          }
        }
      }
    }
  };
});


// plugins/paste.js
///import core
///import plugins/inserthtml.js
///import plugins/undo.js
///import plugins/serialize.js
///commands 粘贴
///commandsName  PastePlain
///commandsTitle  纯文本粘贴模式
/**
 * @description 粘贴
 * @author zhanyi
 */
UE.plugins["paste"] = function() {
  function getClipboardData(callback) {
    var doc = this.document;
    if (doc.getElementById("baidu_pastebin")) {
      return;
    }
    var range = this.selection.getRange(),
      bk = range.createBookmark(),
      //创建剪贴的容器div
      pastebin = doc.createElement("div");
    pastebin.id = "baidu_pastebin";
    // Safari 要求div必须有内容，才能粘贴内容进来
    browser.webkit &&
      pastebin.appendChild(
        doc.createTextNode(domUtils.fillChar + domUtils.fillChar)
      );
    doc.body.appendChild(pastebin);
    //trace:717 隐藏的span不能得到top
    //bk.start.innerHTML = '&nbsp;';
    bk.start.style.display = "";
    pastebin.style.cssText =
      "position:absolute;width:1px;height:1px;overflow:hidden;left:-1000px;white-space:nowrap;top:" +
      //要在现在光标平行的位置加入，否则会出现跳动的问题
      domUtils.getXY(bk.start).y +
      "px";

    range.selectNodeContents(pastebin).select(true);

    setTimeout(function() {
      if (browser.webkit) {
        for (
          var i = 0, pastebins = doc.querySelectorAll("#baidu_pastebin"), pi;
          (pi = pastebins[i++]);

        ) {
          if (domUtils.isEmptyNode(pi)) {
            domUtils.remove(pi);
          } else {
            pastebin = pi;
            break;
          }
        }
      }
      try {
        pastebin.parentNode.removeChild(pastebin);
      } catch (e) {}
      range.moveToBookmark(bk).select(true);
      callback(pastebin);
    }, 0);
  }

  var me = this;

  me.setOpt({
    retainOnlyLabelPasted: false
  });

  var txtContent, htmlContent, address;

  function getPureHtml(html) {
    return html.replace(/<(\/?)([\w\-]+)([^>]*)>/gi, function(
      a,
      b,
      tagName,
      attrs
    ) {
      tagName = tagName.toLowerCase();
      if ({ img: 1 }[tagName]) {
        return a;
      }
      attrs = attrs.replace(
        /([\w\-]*?)\s*=\s*(("([^"]*)")|('([^']*)')|([^\s>]+))/gi,
        function(str, atr, val) {
          if (
            {
              src: 1,
              href: 1,
              name: 1
            }[atr.toLowerCase()]
          ) {
            return atr + "=" + val + " ";
          }
          return "";
        }
      );
      if (
        {
          span: 1,
          div: 1
        }[tagName]
      ) {
        return "";
      } else {
        return "<" + b + tagName + " " + utils.trim(attrs) + ">";
      }
    });
  }
  function filter(div) {
    var html;
    if (div.firstChild) {
      //去掉cut中添加的边界值
      var nodes = domUtils.getElementsByTagName(div, "span");
      for (var i = 0, ni; (ni = nodes[i++]); ) {
        if (ni.id == "_baidu_cut_start" || ni.id == "_baidu_cut_end") {
          domUtils.remove(ni);
        }
      }

      if (browser.webkit) {
        var brs = div.querySelectorAll("div br");
        for (var i = 0, bi; (bi = brs[i++]); ) {
          var pN = bi.parentNode;
          if (pN.tagName == "DIV" && pN.childNodes.length == 1) {
            pN.innerHTML = "<p><br/></p>";
            domUtils.remove(pN);
          }
        }
        var divs = div.querySelectorAll("#baidu_pastebin");
        for (var i = 0, di; (di = divs[i++]); ) {
          var tmpP = me.document.createElement("p");
          di.parentNode.insertBefore(tmpP, di);
          while (di.firstChild) {
            tmpP.appendChild(di.firstChild);
          }
          domUtils.remove(di);
        }

        var metas = div.querySelectorAll("meta");
        for (var i = 0, ci; (ci = metas[i++]); ) {
          domUtils.remove(ci);
        }

        var brs = div.querySelectorAll("br");
        for (i = 0; (ci = brs[i++]); ) {
          if (/^apple-/i.test(ci.className)) {
            domUtils.remove(ci);
          }
        }
      }
      if (browser.gecko) {
        var dirtyNodes = div.querySelectorAll("[_moz_dirty]");
        for (i = 0; (ci = dirtyNodes[i++]); ) {
          ci.removeAttribute("_moz_dirty");
        }
      }
      if (!browser.ie) {
        var spans = div.querySelectorAll("span.Apple-style-span");
        for (var i = 0, ci; (ci = spans[i++]); ) {
          domUtils.remove(ci, true);
        }
      }

      //ie下使用innerHTML会产生多余的\r\n字符，也会产生&nbsp;这里过滤掉
      html = div.innerHTML; //.replace(/>(?:(\s|&nbsp;)*?)</g,'><');

      //过滤word粘贴过来的冗余属性
      html = UE.filterWord(html);
      //取消了忽略空白的第二个参数，粘贴过来的有些是有空白的，会被套上相关的标签
      var root = UE.htmlparser(html);
      //如果给了过滤规则就先进行过滤
      if (me.options.filterRules) {
        UE.filterNode(root, me.options.filterRules);
      }
      //执行默认的处理
      me.filterInputRule(root);
      //针对chrome的处理
      if (browser.webkit) {
        var br = root.lastChild();
        if (br && br.type == "element" && br.tagName == "br") {
          root.removeChild(br);
        }
        utils.each(me.body.querySelectorAll("div"), function(node) {
          if (domUtils.isEmptyBlock(node)) {
            domUtils.remove(node, true);
          }
        });
      }
      html = { html: root.toHtml() };
      me.fireEvent("beforepaste", html, root);
      //抢了默认的粘贴，那后边的内容就不执行了，比如表格粘贴
      if (!html.html) {
        return;
      }
      root = UE.htmlparser(html.html, true);
      //如果开启了纯文本模式
      if (me.queryCommandState("pasteplain") === 1) {
        me.execCommand(
          "insertHtml",
          UE.filterNode(root, me.options.filterTxtRules).toHtml(),
          true
        );
      } else {
        //文本模式
        UE.filterNode(root, me.options.filterTxtRules);
        txtContent = root.toHtml();
        //完全模式
        htmlContent = html.html;

        address = me.selection.getRange().createAddress(true);
        me.execCommand(
          "insertHtml",
          me.getOpt("retainOnlyLabelPasted") === true
            ? getPureHtml(htmlContent)
            : htmlContent,
          true
        );
      }
      me.fireEvent("afterpaste", html);
    }
  }

  me.addListener("pasteTransfer", function(cmd, plainType) {
    if (address && txtContent && htmlContent && txtContent != htmlContent) {
      var range = me.selection.getRange();
      range.moveToAddress(address, true);

      if (!range.collapsed) {
        while (!domUtils.isBody(range.startContainer)) {
          var start = range.startContainer;
          if (start.nodeType == 1) {
            start = start.childNodes[range.startOffset];
            if (!start) {
              range.setStartBefore(range.startContainer);
              continue;
            }
            var pre = start.previousSibling;

            if (
              pre &&
              pre.nodeType == 3 &&
              new RegExp("^[\n\r\t " + domUtils.fillChar + "]*$").test(
                pre.nodeValue
              )
            ) {
              range.setStartBefore(pre);
            }
          }
          if (range.startOffset == 0) {
            range.setStartBefore(range.startContainer);
          } else {
            break;
          }
        }
        while (!domUtils.isBody(range.endContainer)) {
          var end = range.endContainer;
          if (end.nodeType == 1) {
            end = end.childNodes[range.endOffset];
            if (!end) {
              range.setEndAfter(range.endContainer);
              continue;
            }
            var next = end.nextSibling;
            if (
              next &&
              next.nodeType == 3 &&
              new RegExp("^[\n\r\t" + domUtils.fillChar + "]*$").test(
                next.nodeValue
              )
            ) {
              range.setEndAfter(next);
            }
          }
          if (
            range.endOffset ==
            range.endContainer[
              range.endContainer.nodeType == 3 ? "nodeValue" : "childNodes"
            ].length
          ) {
            range.setEndAfter(range.endContainer);
          } else {
            break;
          }
        }
      }

      range.deleteContents();
      range.select(true);
      me.__hasEnterExecCommand = true;
      var html = htmlContent;
      if (plainType === 2) {
        html = getPureHtml(html);
      } else if (plainType) {
        html = txtContent;
      }
      me.execCommand("inserthtml", html, true);
      me.__hasEnterExecCommand = false;
      var rng = me.selection.getRange();
      while (
        !domUtils.isBody(rng.startContainer) &&
        !rng.startOffset &&
        rng.startContainer[
          rng.startContainer.nodeType == 3 ? "nodeValue" : "childNodes"
        ].length
      ) {
        rng.setStartBefore(rng.startContainer);
      }
      var tmpAddress = rng.createAddress(true);
      address.endAddress = tmpAddress.startAddress;
    }
  });

  me.addListener("ready", function() {
    domUtils.on(me.body, "cut", function() {
      var range = me.selection.getRange();
      if (!range.collapsed && me.undoManger) {
        me.undoManger.save();
      }
    });

    //ie下beforepaste在点击右键时也会触发，所以用监控键盘才处理
    domUtils.on(
      me.body,
      browser.ie || browser.opera ? "keydown" : "paste",
      function(e) {
        if (
          (browser.ie || browser.opera) &&
          ((!e.ctrlKey && !e.metaKey) || e.keyCode != "86")
        ) {
          return;
        }
        getClipboardData.call(me, function(div) {
          filter(div);
        });
      }
    );
  });

  me.commands["paste"] = {
    execCommand: function(cmd) {
      if (browser.ie) {
        getClipboardData.call(me, function(div) {
          filter(div);
        });
        me.document.execCommand("paste");
      } else {
        alert(me.getLang("pastemsg"));
      }
    }
  };
};


// plugins/puretxtpaste.js
/**
 * 纯文本粘贴插件
 * @file
 * @since 1.2.6.1
 */

UE.plugins["pasteplain"] = function() {
  var me = this;
  me.setOpt({
    pasteplain: false,
    filterTxtRules: (function() {
      function transP(node) {
        node.tagName = "p";
        node.setStyle();
      }
      function removeNode(node) {
        node.parentNode.removeChild(node, true);
      }
      return {
        //直接删除及其字节点内容
        "-": "script style object iframe embed input select",
        p: { $: {} },
        br: { $: {} },
        div: function(node) {
          var tmpNode,
            p = UE.uNode.createElement("p");
          while ((tmpNode = node.firstChild())) {
            if (tmpNode.type == "text" || !UE.dom.dtd.$block[tmpNode.tagName]) {
              p.appendChild(tmpNode);
            } else {
              if (p.firstChild()) {
                node.parentNode.insertBefore(p, node);
                p = UE.uNode.createElement("p");
              } else {
                node.parentNode.insertBefore(tmpNode, node);
              }
            }
          }
          if (p.firstChild()) {
            node.parentNode.insertBefore(p, node);
          }
          node.parentNode.removeChild(node);
        },
        ol: removeNode,
        ul: removeNode,
        dl: removeNode,
        dt: removeNode,
        dd: removeNode,
        li: removeNode,
        caption: transP,
        th: transP,
        tr: transP,
        h1: transP,
        h2: transP,
        h3: transP,
        h4: transP,
        h5: transP,
        h6: transP,
        td: function(node) {
          //没有内容的td直接删掉
          var txt = !!node.innerText();
          if (txt) {
            node.parentNode.insertAfter(
              UE.uNode.createText(" &nbsp; &nbsp;"),
              node
            );
          }
          node.parentNode.removeChild(node, node.innerText());
        }
      };
    })()
  });
  //暂时这里支持一下老版本的属性
  var pasteplain = me.options.pasteplain;

  /**
     * 启用或取消纯文本粘贴模式
     * @command pasteplain
     * @method execCommand
     * @param { String } cmd 命令字符串
     * @example
     * ```javascript
     * editor.queryCommandState( 'pasteplain' );
     * ```
     */

  /**
     * 查询当前是否处于纯文本粘贴模式
     * @command pasteplain
     * @method queryCommandState
     * @param { String } cmd 命令字符串
     * @return { int } 如果处于纯文本模式，返回1，否则，返回0
     * @example
     * ```javascript
     * editor.queryCommandState( 'pasteplain' );
     * ```
     */
  me.commands["pasteplain"] = {
    queryCommandState: function() {
      return pasteplain ? 1 : 0;
    },
    execCommand: function() {
      pasteplain = !pasteplain | 0;
    },
    notNeedUndo: 1
  };
};


// plugins/list.js
/**
 * 有序列表,无序列表插件
 * @file
 * @since 1.2.6.1
 */

UE.plugins["list"] = function() {
  var me = this,
    notExchange = {
      TD: 1,
      PRE: 1,
      BLOCKQUOTE: 1
    };
  var customStyle = {
    cn: "cn-1-",
    cn1: "cn-2-",
    cn2: "cn-3-",
    num: "num-1-",
    num1: "num-2-",
    num2: "num-3-",
    dash: "dash",
    dot: "dot"
  };

  me.setOpt({
    autoTransWordToList: false,
    insertorderedlist: {
      num: "",
      num1: "",
      num2: "",
      cn: "",
      cn1: "",
      cn2: "",
      decimal: "",
      "lower-alpha": "",
      "lower-roman": "",
      "upper-alpha": "",
      "upper-roman": ""
    },
    insertunorderedlist: {
      circle: "",
      disc: "",
      square: "",
      dash: "",
      dot: ""
    },
    listDefaultPaddingLeft: "30",
    listiconpath: "http://bs.baidu.com/listicon/",
    maxListLevel: -1, //-1不限制
    disablePInList: false
  });
  function listToArray(list) {
    var arr = [];
    for (var p in list) {
      arr.push(p);
    }
    return arr;
  }
  var listStyle = {
    OL: listToArray(me.options.insertorderedlist),
    UL: listToArray(me.options.insertunorderedlist)
  };
  var liiconpath = me.options.listiconpath;

  //根据用户配置，调整customStyle
  for (var s in customStyle) {
    if (
      !me.options.insertorderedlist.hasOwnProperty(s) &&
      !me.options.insertunorderedlist.hasOwnProperty(s)
    ) {
      delete customStyle[s];
    }
  }

  me.ready(function() {
    var customCss = [];
    for (var p in customStyle) {
      if (p == "dash" || p == "dot") {
        customCss.push(
          "li.list-" +
            customStyle[p] +
            "{background-image:url(" +
            liiconpath +
            customStyle[p] +
            ".gif)}"
        );
        customCss.push(
          "ul.custom_" +
            p +
            "{list-style:none;}ul.custom_" +
            p +
            " li{background-position:0 3px;background-repeat:no-repeat}"
        );
      } else {
        for (var i = 0; i < 99; i++) {
          customCss.push(
            "li.list-" +
              customStyle[p] +
              i +
              "{background-image:url(" +
              liiconpath +
              "list-" +
              customStyle[p] +
              i +
              ".gif)}"
          );
        }
        customCss.push(
          "ol.custom_" +
            p +
            "{list-style:none;}ol.custom_" +
            p +
            " li{background-position:0 3px;background-repeat:no-repeat}"
        );
      }
      switch (p) {
        case "cn":
          customCss.push("li.list-" + p + "-paddingleft-1{padding-left:25px}");
          customCss.push("li.list-" + p + "-paddingleft-2{padding-left:40px}");
          customCss.push("li.list-" + p + "-paddingleft-3{padding-left:55px}");
          break;
        case "cn1":
          customCss.push("li.list-" + p + "-paddingleft-1{padding-left:30px}");
          customCss.push("li.list-" + p + "-paddingleft-2{padding-left:40px}");
          customCss.push("li.list-" + p + "-paddingleft-3{padding-left:55px}");
          break;
        case "cn2":
          customCss.push("li.list-" + p + "-paddingleft-1{padding-left:40px}");
          customCss.push("li.list-" + p + "-paddingleft-2{padding-left:55px}");
          customCss.push("li.list-" + p + "-paddingleft-3{padding-left:68px}");
          break;
        case "num":
        case "num1":
          customCss.push("li.list-" + p + "-paddingleft-1{padding-left:25px}");
          break;
        case "num2":
          customCss.push("li.list-" + p + "-paddingleft-1{padding-left:35px}");
          customCss.push("li.list-" + p + "-paddingleft-2{padding-left:40px}");
          break;
        case "dash":
          customCss.push("li.list-" + p + "-paddingleft{padding-left:35px}");
          break;
        case "dot":
          customCss.push("li.list-" + p + "-paddingleft{padding-left:20px}");
      }
    }
    customCss.push(".list-paddingleft-1{padding-left:0}");
    customCss.push(
      ".list-paddingleft-2{padding-left:" +
        me.options.listDefaultPaddingLeft +
        "px}"
    );
    customCss.push(
      ".list-paddingleft-3{padding-left:" +
        me.options.listDefaultPaddingLeft * 2 +
        "px}"
    );
    //如果不给宽度会在自定应样式里出现滚动条
    utils.cssRule(
      "list",
      "ol,ul{margin:0;pading:0;" +
        (browser.ie ? "" : "width:95%") +
        "}li{clear:both;}" +
        customCss.join("\n"),
      me.document
    );
  });
  //单独处理剪切的问题
  me.ready(function() {
    domUtils.on(me.body, "cut", function() {
      setTimeout(function() {
        var rng = me.selection.getRange(),
          li;
        //trace:3416
        if (!rng.collapsed) {
          if (
            (li = domUtils.findParentByTagName(rng.startContainer, "li", true))
          ) {
            if (!li.nextSibling && domUtils.isEmptyBlock(li)) {
              var pn = li.parentNode,
                node;
              if ((node = pn.previousSibling)) {
                domUtils.remove(pn);
                rng.setStartAtLast(node).collapse(true);
                rng.select(true);
              } else if ((node = pn.nextSibling)) {
                domUtils.remove(pn);
                rng.setStartAtFirst(node).collapse(true);
                rng.select(true);
              } else {
                var tmpNode = me.document.createElement("p");
                domUtils.fillNode(me.document, tmpNode);
                pn.parentNode.insertBefore(tmpNode, pn);
                domUtils.remove(pn);
                rng.setStart(tmpNode, 0).collapse(true);
                rng.select(true);
              }
            }
          }
        }
      });
    });
  });

  function getStyle(node) {
    var cls = node.className;
    if (domUtils.hasClass(node, /custom_/)) {
      return cls.match(/custom_(\w+)/)[1];
    }
    return domUtils.getStyle(node, "list-style-type");
  }

  me.addListener("beforepaste", function(type, html) {
    var me = this,
      rng = me.selection.getRange(),
      li;
    var root = UE.htmlparser(html.html, true);
    if ((li = domUtils.findParentByTagName(rng.startContainer, "li", true))) {
      var list = li.parentNode,
        tagName = list.tagName === "OL" ? "ul" : "ol";
      utils.each(root.getNodesByTagName(tagName), function(n) {
        n.tagName = list.tagName;
        n.setAttr();
        if (n.parentNode === root) {
          type = getStyle(list) || (list.tagName == "OL" ? "decimal" : "disc");
        } else {
          var className = n.parentNode.getAttr("class");
          if (className && /custom_/.test(className)) {
            type = className.match(/custom_(\w+)/)[1];
          } else {
            type = n.parentNode.getStyle("list-style-type");
          }
          if (!type) {
            type = list.tagName === "OL" ? "decimal" : "disc";
          }
        }
        var index = utils.indexOf(listStyle[list.tagName], type);
        if (n.parentNode !== root)
          index = index + 1 === listStyle[list.tagName].length ? 0 : index + 1;
        var currentStyle = listStyle[list.tagName][index];
        if (customStyle[currentStyle]) {
          n.setAttr("class", "custom_" + currentStyle);
        } else {
          n.setStyle("list-style-type", currentStyle);
        }
      });
    }

    html.html = root.toHtml();
  });
  //导出时，去掉p标签
  me.getOpt("disablePInList") === true &&
    me.addOutputRule(function(root) {
      utils.each(root.getNodesByTagName("li"), function(li) {
        var newChildrens = [],
          index = 0;
        utils.each(li.children, function(n) {
          if (n.tagName == "p") {
            var tmpNode;
            while ((tmpNode = n.children.pop())) {
              newChildrens.splice(index, 0, tmpNode);
              tmpNode.parentNode = li;
              lastNode = tmpNode;
            }
            tmpNode = newChildrens[newChildrens.length - 1];
            if (
              !tmpNode ||
              tmpNode.type !== "element" ||
              tmpNode.tagName !== "br"
            ) {
              var br = UE.uNode.createElement("br");
              br.parentNode = li;
              newChildrens.push(br);
            }

            index = newChildrens.length;
          }
        });
        if (newChildrens.length) {
          li.children = newChildrens;
        }
      });
    });
  //进入编辑器的li要套p标签
  me.addInputRule(function(root) {
    utils.each(root.getNodesByTagName("li"), function(li) {
      var tmpP = UE.uNode.createElement("p");
      for (var i = 0, ci; (ci = li.children[i]); ) {
        if (ci.type === "text" || dtd.p[ci.tagName]) {
          tmpP.appendChild(ci);
        } else {
          if (tmpP.firstChild()) {
            li.insertBefore(tmpP, ci);
            tmpP = UE.uNode.createElement("p");
            i = i + 2;
          } else {
            i++;
          }
        }
      }
      if ((tmpP.firstChild() && !tmpP.parentNode) || !li.firstChild()) {
        li.appendChild(tmpP);
      }
      //trace:3357
      //p不能为空
      if (!tmpP.firstChild()) {
        tmpP.innerHTML(browser.ie ? "&nbsp;" : "<br/>");
      }
      //去掉末尾的空白
      var p = li.firstChild();
      var lastChild = p.lastChild();
      if (
        lastChild &&
        lastChild.type === "text" &&
        /^\s*$/.test(lastChild.data)
      ) {
        p.removeChild(lastChild);
      }
    });
    if (me.options.autoTransWordToList) {
      var orderlisttype = {
        num1: /^\d+\)/,
        decimal: /^\d+\./,
        "lower-alpha": /^[a-z]+\)/,
        "upper-alpha": /^[A-Z]+\./,
        cn: /^[\u4E00\u4E8C\u4E09\u56DB\u516d\u4e94\u4e03\u516b\u4e5d]+[\u3001]/,
        cn2: /^\([\u4E00\u4E8C\u4E09\u56DB\u516d\u4e94\u4e03\u516b\u4e5d]+\)/
      },
        unorderlisttype = {
          square: "n"
        };
      function checkListType(content, container) {
        var span = container.firstChild();
        if (
          span &&
          span.type === "element" &&
          span.tagName === "span" &&
          /Wingdings|Symbol/.test(span.getStyle("font-family"))
        ) {
          for (var p in unorderlisttype) {
            if (unorderlisttype[p] == span.data) {
              return p;
            }
          }
          return "disc";
        }
        for (var p in orderlisttype) {
          if (orderlisttype[p].test(content)) {
            return p;
          }
        }
      }
      utils.each(root.getNodesByTagName("p"), function(node) {
        if (node.getAttr("class") !== "MsoListParagraph") {
          return;
        }

        //word粘贴过来的会带有margin要去掉,但这样也可能会误命中一些央视
        node.setStyle("margin", "");
        node.setStyle("margin-left", "");
        node.setAttr("class", "");

        function appendLi(list, p, type) {
          if (list.tagName === "ol") {
            if (browser.ie) {
              var first = p.firstChild();
              if (
                first.type === "element" &&
                first.tagName === "span" &&
                orderlisttype[type].test(first.innerText())
              ) {
                p.removeChild(first);
              }
            } else {
              p.innerHTML(p.innerHTML().replace(orderlisttype[type], ""));
            }
          } else {
            p.removeChild(p.firstChild());
          }

          var li = UE.uNode.createElement("li");
          li.appendChild(p);
          list.appendChild(li);
        }
        var tmp = node,
          type,
          cacheNode = node;

        if (
          node.parentNode.tagName !== "li" &&
          (type = checkListType(node.innerText(), node))
        ) {
          var list = UE.uNode.createElement(
            me.options.insertorderedlist.hasOwnProperty(type) ? "ol" : "ul"
          );
          if (customStyle[type]) {
            list.setAttr("class", "custom_" + type);
          } else {
            list.setStyle("list-style-type", type);
          }
          while (
            node &&
            node.parentNode.tagName !== "li" &&
            checkListType(node.innerText(), node)
          ) {
            tmp = node.nextSibling();
            if (!tmp) {
              node.parentNode.insertBefore(list, node);
            }
            appendLi(list, node, type);
            node = tmp;
          }
          if (!list.parentNode && node && node.parentNode) {
            node.parentNode.insertBefore(list, node);
          }
        }
        var span = cacheNode.firstChild();
        if (
          span &&
          span.type == "element" &&
          span.tagName == "span" &&
          /^\s*(&nbsp;)+\s*$/.test(span.innerText())
        ) {
          span.parentNode.removeChild(span);
        }
      });
    }
  });

  //调整索引标签
  me.addListener("contentchange", function() {
    adjustListStyle(me.document);
  });

  function adjustListStyle(doc, ignore) {
    utils.each(domUtils.getElementsByTagName(doc, "ol ul"), function(node) {
      if (!domUtils.inDoc(node, doc)) return;

      var parent = node.parentNode;
      if (parent.tagName === node.tagName) {
        var nodeStyleType =
          getStyle(node) || (node.tagName === "OL" ? "decimal" : "disc"),
          parentStyleType =
            getStyle(parent) || (parent.tagName === "OL" ? "decimal" : "disc");
        if (nodeStyleType === parentStyleType) {
          var styleIndex = utils.indexOf(
            listStyle[node.tagName],
            nodeStyleType
          );
          styleIndex = styleIndex + 1 === listStyle[node.tagName].length
            ? 0
            : styleIndex + 1;
          setListStyle(node, listStyle[node.tagName][styleIndex]);
        }
      }
      var index = 0,
        type = 2;
      if (domUtils.hasClass(node, /custom_/)) {
        if (
          !(
            /[ou]l/i.test(parent.tagName) &&
            domUtils.hasClass(parent, /custom_/)
          )
        ) {
          type = 1;
        }
      } else {
        if (
          /[ou]l/i.test(parent.tagName) &&
          domUtils.hasClass(parent, /custom_/)
        ) {
          type = 3;
        }
      }

      var style = domUtils.getStyle(node, "list-style-type");
      style && (node.style.cssText = "list-style-type:" + style);
      node.className =
        utils.trim(node.className.replace(/list-paddingleft-\w+/, "")) +
        " list-paddingleft-" +
        type;
      utils.each(domUtils.getElementsByTagName(node, "li"), function(li) {
        li.style.cssText && (li.style.cssText = "");
        if (!li.firstChild) {
          domUtils.remove(li);
          return;
        }
        if (li.parentNode !== node) {
          return;
        }
        index++;
        if (domUtils.hasClass(node, /custom_/)) {
          var paddingLeft = 1,
            currentStyle = getStyle(node);
          if (node.tagName === "OL") {
            if (currentStyle) {
              switch (currentStyle) {
                case "cn":
                case "cn1":
                case "cn2":
                  if (
                    index > 10 &&
                    (index % 10 === 0 || (index > 10 && index < 20))
                  ) {
                    paddingLeft = 2;
                  } else if (index > 20) {
                    paddingLeft = 3;
                  }
                  break;
                case "num2":
                  if (index > 9) {
                    paddingLeft = 2;
                  }
              }
            }
            li.className =
              "list-" +
              customStyle[currentStyle] +
              index +
              " " +
              "list-" +
              currentStyle +
              "-paddingleft-" +
              paddingLeft;
          } else {
            li.className =
              "list-" +
              customStyle[currentStyle] +
              " " +
              "list-" +
              currentStyle +
              "-paddingleft";
          }
        } else {
          li.className = li.className.replace(/list-[\w\-]+/gi, "");
        }
        var className = li.getAttribute("class");
        if (className !== null && !className.replace(/\s/g, "")) {
          domUtils.removeAttributes(li, "class");
        }
      });
      !ignore &&
        adjustList(
          node,
          node.tagName.toLowerCase(),
          getStyle(node) || domUtils.getStyle(node, "list-style-type"),
          true
        );
    });
  }
  function adjustList(list, tag, style, ignoreEmpty) {
    var nextList = list.nextSibling;
    if (
      nextList &&
      nextList.nodeType === 1 &&
      nextList.tagName.toLowerCase() === tag &&
      (getStyle(nextList) ||
        domUtils.getStyle(nextList, "list-style-type") ||
        (tag == "ol" ? "decimal" : "disc")) == style
    ) {
      domUtils.moveChild(nextList, list);
      if (nextList.childNodes.length === 0) {
        domUtils.remove(nextList);
      }
    }
    if (nextList && domUtils.isFillChar(nextList)) {
      domUtils.remove(nextList);
    }
    var preList = list.previousSibling;
    if (
      preList &&
      preList.nodeType === 1 &&
      preList.tagName.toLowerCase() == tag &&
      (getStyle(preList) ||
        domUtils.getStyle(preList, "list-style-type") ||
        (tag == "ol" ? "decimal" : "disc")) === style
    ) {
      domUtils.moveChild(list, preList);
    }
    if (preList && domUtils.isFillChar(preList)) {
      domUtils.remove(preList);
    }
    !ignoreEmpty && domUtils.isEmptyBlock(list) && domUtils.remove(list);
    if (getStyle(list)) {
      adjustListStyle(list.ownerDocument, true);
    }
  }

  function setListStyle(list, style) {
    if (customStyle[style]) {
      list.className = "custom_" + style;
    }
    try {
      domUtils.setStyle(list, "list-style-type", style);
    } catch (e) {}
  }
  function clearEmptySibling(node) {
    var tmpNode = node.previousSibling;
    if (tmpNode && domUtils.isEmptyBlock(tmpNode)) {
      domUtils.remove(tmpNode);
    }
    tmpNode = node.nextSibling;
    if (tmpNode && domUtils.isEmptyBlock(tmpNode)) {
      domUtils.remove(tmpNode);
    }
  }

  me.addListener("keydown", function(type, evt) {
    function preventAndSave() {
      evt.preventDefault ? evt.preventDefault() : (evt.returnValue = false);
      me.fireEvent("contentchange");
      me.undoManger && me.undoManger.save();
    }
    function findList(node, filterFn) {
      while (node && !domUtils.isBody(node)) {
        if (filterFn(node)) {
          return null;
        }
        if (node.nodeType === 1 && /[ou]l/i.test(node.tagName)) {
          return node;
        }
        node = node.parentNode;
      }
      return null;
    }
    var keyCode = evt.keyCode || evt.which;
    if (keyCode === 13 && !evt.shiftKey) {
      //回车
      var rng = me.selection.getRange(),
        parent = domUtils.findParent(
          rng.startContainer,
          function(node) {
            return domUtils.isBlockElm(node);
          },
          true
        ),
        li = domUtils.findParentByTagName(rng.startContainer, "li", true);
      if (parent && parent.tagName !== "PRE" && !li) {
        var html = parent.innerHTML.replace(
          new RegExp(domUtils.fillChar, "g"),
          ""
        );
        if (/^\s*1\s*\.[^\d]/.test(html)) {
          parent.innerHTML = html.replace(/^\s*1\s*\./, "");
          rng.setStartAtLast(parent).collapse(true).select();
          me.__hasEnterExecCommand = true;
          me.execCommand("insertorderedlist");
          me.__hasEnterExecCommand = false;
        }
      }
      var range = me.selection.getRange(),
        start = findList(range.startContainer, function(node) {
          return node.tagName === "TABLE";
        }),
        end = range.collapsed
          ? start
          : findList(range.endContainer, function(node) {
              return node.tagName === "TABLE";
            });

      if (start && end && start === end) {
        if (!range.collapsed) {
          start = domUtils.findParentByTagName(
            range.startContainer,
            "li",
            true
          );
          end = domUtils.findParentByTagName(range.endContainer, "li", true);
          if (start && end && start === end) {
            range.deleteContents();
            li = domUtils.findParentByTagName(range.startContainer, "li", true);
            if (li && domUtils.isEmptyBlock(li)) {
              pre = li.previousSibling;
              next = li.nextSibling;
              p = me.document.createElement("p");

              domUtils.fillNode(me.document, p);
              parentList = li.parentNode;
              if (pre && next) {
                range.setStart(next, 0).collapse(true).select(true);
                domUtils.remove(li);
              } else {
                if ((!pre && !next) || !pre) {
                  parentList.parentNode.insertBefore(p, parentList);
                } else {
                  li.parentNode.parentNode.insertBefore(
                    p,
                    parentList.nextSibling
                  );
                }
                domUtils.remove(li);
                if (!parentList.firstChild) {
                  domUtils.remove(parentList);
                }
                range.setStart(p, 0).setCursor();
              }
              preventAndSave();
              return;
            }
          } else {
            var tmpRange = range.cloneRange(),
              bk = tmpRange.collapse(false).createBookmark();

            range.deleteContents();
            tmpRange.moveToBookmark(bk);
            var li = domUtils.findParentByTagName(
              tmpRange.startContainer,
              "li",
              true
            );

            clearEmptySibling(li);
            tmpRange.select();
            preventAndSave();
            return;
          }
        }

        li = domUtils.findParentByTagName(range.startContainer, "li", true);

        if (li) {
          if (domUtils.isEmptyBlock(li)) {
            bk = range.createBookmark();
            var parentList = li.parentNode;
            if (li !== parentList.lastChild) {
              domUtils.breakParent(li, parentList);
              clearEmptySibling(li);
            } else {
              parentList.parentNode.insertBefore(li, parentList.nextSibling);
              if (domUtils.isEmptyNode(parentList)) {
                domUtils.remove(parentList);
              }
            }
            //嵌套不处理
            if (!dtd.$list[li.parentNode.tagName]) {
              if (!domUtils.isBlockElm(li.firstChild)) {
                p = me.document.createElement("p");
                li.parentNode.insertBefore(p, li);
                while (li.firstChild) {
                  p.appendChild(li.firstChild);
                }
                domUtils.remove(li);
              } else {
                domUtils.remove(li, true);
              }
            }
            range.moveToBookmark(bk).select();
          } else {
            var first = li.firstChild;
            if (!first || !domUtils.isBlockElm(first)) {
              var p = me.document.createElement("p");

              !li.firstChild && domUtils.fillNode(me.document, p);
              while (li.firstChild) {
                p.appendChild(li.firstChild);
              }
              li.appendChild(p);
              first = p;
            }

            var span = me.document.createElement("span");

            range.insertNode(span);
            domUtils.breakParent(span, li);

            var nextLi = span.nextSibling;
            first = nextLi.firstChild;

            if (!first) {
              p = me.document.createElement("p");

              domUtils.fillNode(me.document, p);
              nextLi.appendChild(p);
              first = p;
            }
            if (domUtils.isEmptyNode(first)) {
              first.innerHTML = "";
              domUtils.fillNode(me.document, first);
            }

            range.setStart(first, 0).collapse(true).shrinkBoundary().select();
            domUtils.remove(span);
            var pre = nextLi.previousSibling;
            if (pre && domUtils.isEmptyBlock(pre)) {
              pre.innerHTML = "<p></p>";
              domUtils.fillNode(me.document, pre.firstChild);
            }
          }
          //                        }
          preventAndSave();
        }
      }
    }
    if (keyCode === 8) {
      //修中ie中li下的问题
      range = me.selection.getRange();
      if (range.collapsed && domUtils.isStartInblock(range)) {
        tmpRange = range.cloneRange().trimBoundary();
        li = domUtils.findParentByTagName(range.startContainer, "li", true);
        //要在li的最左边，才能处理
        if (li && domUtils.isStartInblock(tmpRange)) {
          start = domUtils.findParentByTagName(range.startContainer, "p", true);
          if (start && start !== li.firstChild) {
            var parentList = domUtils.findParentByTagName(start, ["ol", "ul"]);
            domUtils.breakParent(start, parentList);
            clearEmptySibling(start);
            me.fireEvent("contentchange");
            range.setStart(start, 0).setCursor(false, true);
            me.fireEvent("saveScene");
            domUtils.preventDefault(evt);
            return;
          }

          if (li && (pre = li.previousSibling)) {
            if (keyCode === 46 && li.childNodes.length) {
              return;
            }
            //有可能上边的兄弟节点是个2级菜单，要追加到2级菜单的最后的li
            if (dtd.$list[pre.tagName]) {
              pre = pre.lastChild;
            }
            me.undoManger && me.undoManger.save();
            first = li.firstChild;
            if (domUtils.isBlockElm(first)) {
              if (domUtils.isEmptyNode(first)) {
                //                                    range.setEnd(pre, pre.childNodes.length).shrinkBoundary().collapse().select(true);
                pre.appendChild(first);
                range.setStart(first, 0).setCursor(false, true);
                //first不是唯一的节点
                while (li.firstChild) {
                  pre.appendChild(li.firstChild);
                }
              } else {
                span = me.document.createElement("span");
                range.insertNode(span);
                //判断pre是否是空的节点,如果是<p><br/></p>类型的空节点，干掉p标签防止它占位
                if (domUtils.isEmptyBlock(pre)) {
                  pre.innerHTML = "";
                }
                domUtils.moveChild(li, pre);
                range.setStartBefore(span).collapse(true).select(true);

                domUtils.remove(span);
              }
            } else {
              if (domUtils.isEmptyNode(li)) {
                var p = me.document.createElement("p");
                pre.appendChild(p);
                range.setStart(p, 0).setCursor();
                //                                    range.setEnd(pre, pre.childNodes.length).shrinkBoundary().collapse().select(true);
              } else {
                range
                  .setEnd(pre, pre.childNodes.length)
                  .collapse()
                  .select(true);
                while (li.firstChild) {
                  pre.appendChild(li.firstChild);
                }
              }
            }
            domUtils.remove(li);
            me.fireEvent("contentchange");
            me.fireEvent("saveScene");
            domUtils.preventDefault(evt);
            return;
          }
          //trace:980

          if (li && !li.previousSibling) {
            var parentList = li.parentNode;
            var bk = range.createBookmark();
            if (domUtils.isTagNode(parentList.parentNode, "ol ul")) {
              parentList.parentNode.insertBefore(li, parentList);
              if (domUtils.isEmptyNode(parentList)) {
                domUtils.remove(parentList);
              }
            } else {
              while (li.firstChild) {
                parentList.parentNode.insertBefore(li.firstChild, parentList);
              }

              domUtils.remove(li);
              if (domUtils.isEmptyNode(parentList)) {
                domUtils.remove(parentList);
              }
            }
            range.moveToBookmark(bk).setCursor(false, true);
            me.fireEvent("contentchange");
            me.fireEvent("saveScene");
            domUtils.preventDefault(evt);
            return;
          }
        }
      }
    }
  });

  me.addListener("keyup", function(type, evt) {
    var keyCode = evt.keyCode || evt.which;
    if (keyCode == 8) {
      var rng = me.selection.getRange(),
        list;
      if (
        (list = domUtils.findParentByTagName(
          rng.startContainer,
          ["ol", "ul"],
          true
        ))
      ) {
        adjustList(
          list,
          list.tagName.toLowerCase(),
          getStyle(list) || domUtils.getComputedStyle(list, "list-style-type"),
          true
        );
      }
    }
  });
  //处理tab键
  me.addListener("tabkeydown", function() {
    var range = me.selection.getRange();

    //控制级数
    function checkLevel(li) {
      if (me.options.maxListLevel != -1) {
        var level = li.parentNode,
          levelNum = 0;
        while (/[ou]l/i.test(level.tagName)) {
          levelNum++;
          level = level.parentNode;
        }
        if (levelNum >= me.options.maxListLevel) {
          return true;
        }
      }
    }
    //只以开始为准
    //todo 后续改进
    var li = domUtils.findParentByTagName(range.startContainer, "li", true);
    if (li) {
      var bk;
      if (range.collapsed) {
        if (checkLevel(li)) return true;
        var parentLi = li.parentNode,
          list = me.document.createElement(parentLi.tagName),
          index = utils.indexOf(
            listStyle[list.tagName],
            getStyle(parentLi) ||
              domUtils.getComputedStyle(parentLi, "list-style-type")
          );
        index = index + 1 == listStyle[list.tagName].length ? 0 : index + 1;
        var currentStyle = listStyle[list.tagName][index];
        setListStyle(list, currentStyle);
        if (domUtils.isStartInblock(range)) {
          me.fireEvent("saveScene");
          bk = range.createBookmark();
          parentLi.insertBefore(list, li);
          list.appendChild(li);
          adjustList(list, list.tagName.toLowerCase(), currentStyle);
          me.fireEvent("contentchange");
          range.moveToBookmark(bk).select(true);
          return true;
        }
      } else {
        me.fireEvent("saveScene");
        bk = range.createBookmark();
        for (
          var i = 0, closeList, parents = domUtils.findParents(li), ci;
          (ci = parents[i++]);

        ) {
          if (domUtils.isTagNode(ci, "ol ul")) {
            closeList = ci;
            break;
          }
        }
        var current = li;
        if (bk.end) {
          while (
            current &&
            !(
              domUtils.getPosition(current, bk.end) &
              domUtils.POSITION_FOLLOWING
            )
          ) {
            if (checkLevel(current)) {
              current = domUtils.getNextDomNode(current, false, null, function(
                node
              ) {
                return node !== closeList;
              });
              continue;
            }
            var parentLi = current.parentNode,
              list = me.document.createElement(parentLi.tagName),
              index = utils.indexOf(
                listStyle[list.tagName],
                getStyle(parentLi) ||
                  domUtils.getComputedStyle(parentLi, "list-style-type")
              );
            var currentIndex = index + 1 == listStyle[list.tagName].length
              ? 0
              : index + 1;
            var currentStyle = listStyle[list.tagName][currentIndex];
            setListStyle(list, currentStyle);
            parentLi.insertBefore(list, current);
            while (
              current &&
              !(
                domUtils.getPosition(current, bk.end) &
                domUtils.POSITION_FOLLOWING
              )
            ) {
              li = current.nextSibling;
              list.appendChild(current);
              if (!li || domUtils.isTagNode(li, "ol ul")) {
                if (li) {
                  while ((li = li.firstChild)) {
                    if (li.tagName == "LI") {
                      break;
                    }
                  }
                } else {
                  li = domUtils.getNextDomNode(current, false, null, function(
                    node
                  ) {
                    return node !== closeList;
                  });
                }
                break;
              }
              current = li;
            }
            adjustList(list, list.tagName.toLowerCase(), currentStyle);
            current = li;
          }
        }
        me.fireEvent("contentchange");
        range.moveToBookmark(bk).select();
        return true;
      }
    }
  });
  function getLi(start) {
    while (start && !domUtils.isBody(start)) {
      if (start.nodeName == "TABLE") {
        return null;
      }
      if (start.nodeName == "LI") {
        return start;
      }
      start = start.parentNode;
    }
  }

  /**
     * 有序列表，与“insertunorderedlist”命令互斥
     * @command insertorderedlist
     * @method execCommand
     * @param { String } command 命令字符串
     * @param { String } style 插入的有序列表类型，值为：decimal,lower-alpha,lower-roman,upper-alpha,upper-roman,cn,cn1,cn2,num,num1,num2
     * @example
     * ```javascript
     * editor.execCommand( 'insertorderedlist','decimal');
     * ```
     */
  /**
     * 查询当前选区内容是否有序列表
     * @command insertorderedlist
     * @method queryCommandState
     * @param { String } cmd 命令字符串
     * @return { int } 如果当前选区是有序列表返回1，否则返回0
     * @example
     * ```javascript
     * editor.queryCommandState( 'insertorderedlist' );
     * ```
     */
  /**
     * 查询当前选区内容是否有序列表
     * @command insertorderedlist
     * @method queryCommandValue
     * @param { String } cmd 命令字符串
     * @return { String } 返回当前有序列表的类型，值为null或decimal,lower-alpha,lower-roman,upper-alpha,upper-roman,cn,cn1,cn2,num,num1,num2
     * @example
     * ```javascript
     * editor.queryCommandValue( 'insertorderedlist' );
     * ```
     */

  /**
     * 无序列表，与“insertorderedlist”命令互斥
     * @command insertunorderedlist
     * @method execCommand
     * @param { String } command 命令字符串
     * @param { String } style 插入的无序列表类型，值为：circle,disc,square,dash,dot
     * @example
     * ```javascript
     * editor.execCommand( 'insertunorderedlist','circle');
     * ```
     */
  /**
     * 查询当前是否有word文档粘贴进来的图片
     * @command insertunorderedlist
     * @method insertunorderedlist
     * @param { String } command 命令字符串
     * @return { int } 如果当前选区是无序列表返回1，否则返回0
     * @example
     * ```javascript
     * editor.queryCommandState( 'insertunorderedlist' );
     * ```
     */
  /**
     * 查询当前选区内容是否有序列表
     * @command insertunorderedlist
     * @method queryCommandValue
     * @param { String } command 命令字符串
     * @return { String } 返回当前无序列表的类型，值为null或circle,disc,square,dash,dot
     * @example
     * ```javascript
     * editor.queryCommandValue( 'insertunorderedlist' );
     * ```
     */

  me.commands["insertorderedlist"] = me.commands["insertunorderedlist"] = {
    execCommand: function(command, style) {
      if (!style) {
        style = command.toLowerCase() == "insertorderedlist"
          ? "decimal"
          : "disc";
      }
      var me = this,
        range = this.selection.getRange(),
        filterFn = function(node) {
          return node.nodeType == 1
            ? node.tagName.toLowerCase() != "br"
            : !domUtils.isWhitespace(node);
        },
        tag = command.toLowerCase() == "insertorderedlist" ? "ol" : "ul",
        frag = me.document.createDocumentFragment();
      //去掉是因为会出现选到末尾，导致adjustmentBoundary缩到ol/ul的位置
      //range.shrinkBoundary();//.adjustmentBoundary();
      range.adjustmentBoundary().shrinkBoundary();
      var bko = range.createBookmark(true),
        start = getLi(me.document.getElementById(bko.start)),
        modifyStart = 0,
        end = getLi(me.document.getElementById(bko.end)),
        modifyEnd = 0,
        startParent,
        endParent,
        list,
        tmp;

      if (start || end) {
        start && (startParent = start.parentNode);
        if (!bko.end) {
          end = start;
        }
        end && (endParent = end.parentNode);

        if (startParent === endParent) {
          while (start !== end) {
            tmp = start;
            start = start.nextSibling;
            if (!domUtils.isBlockElm(tmp.firstChild)) {
              var p = me.document.createElement("p");
              while (tmp.firstChild) {
                p.appendChild(tmp.firstChild);
              }
              tmp.appendChild(p);
            }
            frag.appendChild(tmp);
          }
          tmp = me.document.createElement("span");
          startParent.insertBefore(tmp, end);
          if (!domUtils.isBlockElm(end.firstChild)) {
            p = me.document.createElement("p");
            while (end.firstChild) {
              p.appendChild(end.firstChild);
            }
            end.appendChild(p);
          }
          frag.appendChild(end);
          domUtils.breakParent(tmp, startParent);
          if (domUtils.isEmptyNode(tmp.previousSibling)) {
            domUtils.remove(tmp.previousSibling);
          }
          if (domUtils.isEmptyNode(tmp.nextSibling)) {
            domUtils.remove(tmp.nextSibling);
          }
          var nodeStyle =
            getStyle(startParent) ||
            domUtils.getComputedStyle(startParent, "list-style-type") ||
            (command.toLowerCase() == "insertorderedlist" ? "decimal" : "disc");
          if (startParent.tagName.toLowerCase() == tag && nodeStyle == style) {
            for (
              var i = 0, ci, tmpFrag = me.document.createDocumentFragment();
              (ci = frag.firstChild);

            ) {
              if (domUtils.isTagNode(ci, "ol ul")) {
                //                                  删除时，子列表不处理
                //                                  utils.each(domUtils.getElementsByTagName(ci,'li'),function(li){
                //                                        while(li.firstChild){
                //                                            tmpFrag.appendChild(li.firstChild);
                //                                        }
                //
                //                                    });
                tmpFrag.appendChild(ci);
              } else {
                while (ci.firstChild) {
                  tmpFrag.appendChild(ci.firstChild);
                  domUtils.remove(ci);
                }
              }
            }
            tmp.parentNode.insertBefore(tmpFrag, tmp);
          } else {
            list = me.document.createElement(tag);
            setListStyle(list, style);
            list.appendChild(frag);
            tmp.parentNode.insertBefore(list, tmp);
          }

          domUtils.remove(tmp);
          list && adjustList(list, tag, style);
          range.moveToBookmark(bko).select();
          return;
        }
        //开始
        if (start) {
          while (start) {
            tmp = start.nextSibling;
            if (domUtils.isTagNode(start, "ol ul")) {
              frag.appendChild(start);
            } else {
              var tmpfrag = me.document.createDocumentFragment(),
                hasBlock = 0;
              while (start.firstChild) {
                if (domUtils.isBlockElm(start.firstChild)) {
                  hasBlock = 1;
                }
                tmpfrag.appendChild(start.firstChild);
              }
              if (!hasBlock) {
                var tmpP = me.document.createElement("p");
                tmpP.appendChild(tmpfrag);
                frag.appendChild(tmpP);
              } else {
                frag.appendChild(tmpfrag);
              }
              domUtils.remove(start);
            }

            start = tmp;
          }
          startParent.parentNode.insertBefore(frag, startParent.nextSibling);
          if (domUtils.isEmptyNode(startParent)) {
            range.setStartBefore(startParent);
            domUtils.remove(startParent);
          } else {
            range.setStartAfter(startParent);
          }
          modifyStart = 1;
        }

        if (end && domUtils.inDoc(endParent, me.document)) {
          //结束
          start = endParent.firstChild;
          while (start && start !== end) {
            tmp = start.nextSibling;
            if (domUtils.isTagNode(start, "ol ul")) {
              frag.appendChild(start);
            } else {
              tmpfrag = me.document.createDocumentFragment();
              hasBlock = 0;
              while (start.firstChild) {
                if (domUtils.isBlockElm(start.firstChild)) {
                  hasBlock = 1;
                }
                tmpfrag.appendChild(start.firstChild);
              }
              if (!hasBlock) {
                tmpP = me.document.createElement("p");
                tmpP.appendChild(tmpfrag);
                frag.appendChild(tmpP);
              } else {
                frag.appendChild(tmpfrag);
              }
              domUtils.remove(start);
            }
            start = tmp;
          }
          var tmpDiv = domUtils.createElement(me.document, "div", {
            tmpDiv: 1
          });
          domUtils.moveChild(end, tmpDiv);

          frag.appendChild(tmpDiv);
          domUtils.remove(end);
          endParent.parentNode.insertBefore(frag, endParent);
          range.setEndBefore(endParent);
          if (domUtils.isEmptyNode(endParent)) {
            domUtils.remove(endParent);
          }

          modifyEnd = 1;
        }
      }

      if (!modifyStart) {
        range.setStartBefore(me.document.getElementById(bko.start));
      }
      if (bko.end && !modifyEnd) {
        range.setEndAfter(me.document.getElementById(bko.end));
      }
      range.enlarge(true, function(node) {
        return notExchange[node.tagName];
      });

      frag = me.document.createDocumentFragment();

      var bk = range.createBookmark(),
        current = domUtils.getNextDomNode(bk.start, false, filterFn),
        tmpRange = range.cloneRange(),
        tmpNode,
        block = domUtils.isBlockElm;

      while (
        current &&
        current !== bk.end &&
        domUtils.getPosition(current, bk.end) & domUtils.POSITION_PRECEDING
      ) {
        if (current.nodeType == 3 || dtd.li[current.tagName]) {
          if (current.nodeType == 1 && dtd.$list[current.tagName]) {
            while (current.firstChild) {
              frag.appendChild(current.firstChild);
            }
            tmpNode = domUtils.getNextDomNode(current, false, filterFn);
            domUtils.remove(current);
            current = tmpNode;
            continue;
          }
          tmpNode = current;
          tmpRange.setStartBefore(current);

          while (
            current &&
            current !== bk.end &&
            (!block(current) || domUtils.isBookmarkNode(current))
          ) {
            tmpNode = current;
            current = domUtils.getNextDomNode(current, false, null, function(
              node
            ) {
              return !notExchange[node.tagName];
            });
          }

          if (current && block(current)) {
            tmp = domUtils.getNextDomNode(tmpNode, false, filterFn);
            if (tmp && domUtils.isBookmarkNode(tmp)) {
              current = domUtils.getNextDomNode(tmp, false, filterFn);
              tmpNode = tmp;
            }
          }
          tmpRange.setEndAfter(tmpNode);

          current = domUtils.getNextDomNode(tmpNode, false, filterFn);

          var li = range.document.createElement("li");

          li.appendChild(tmpRange.extractContents());
          if (domUtils.isEmptyNode(li)) {
            var tmpNode = range.document.createElement("p");
            while (li.firstChild) {
              tmpNode.appendChild(li.firstChild);
            }
            li.appendChild(tmpNode);
          }
          frag.appendChild(li);
        } else {
          current = domUtils.getNextDomNode(current, true, filterFn);
        }
      }
      range.moveToBookmark(bk).collapse(true);
      list = me.document.createElement(tag);
      setListStyle(list, style);
      list.appendChild(frag);
      range.insertNode(list);
      //当前list上下看能否合并
      adjustList(list, tag, style);
      //去掉冗余的tmpDiv
      for (
        var i = 0, ci, tmpDivs = domUtils.getElementsByTagName(list, "div");
        (ci = tmpDivs[i++]);

      ) {
        if (ci.getAttribute("tmpDiv")) {
          domUtils.remove(ci, true);
        }
      }
      range.moveToBookmark(bko).select();
    },
    queryCommandState: function(command) {
      var tag = command.toLowerCase() == "insertorderedlist" ? "ol" : "ul";
      var path = this.selection.getStartElementPath();
      for (var i = 0, ci; (ci = path[i++]); ) {
        if (ci.nodeName == "TABLE") {
          return 0;
        }
        if (tag == ci.nodeName.toLowerCase()) {
          return 1;
        }
      }
      return 0;
    },
    queryCommandValue: function(command) {
      var tag = command.toLowerCase() == "insertorderedlist" ? "ol" : "ul";
      var path = this.selection.getStartElementPath(),
        node;
      for (var i = 0, ci; (ci = path[i++]); ) {
        if (ci.nodeName == "TABLE") {
          node = null;
          break;
        }
        if (tag == ci.nodeName.toLowerCase()) {
          node = ci;
          break;
        }
      }
      return node
        ? getStyle(node) || domUtils.getComputedStyle(node, "list-style-type")
        : null;
    }
  };
};


// plugins/source.js
/**
 * 源码编辑插件
 * @file
 * @since 1.2.6.1
 */

(function() {
  var sourceEditors = {
    textarea: function(editor, holder) {
      var textarea = holder.ownerDocument.createElement("textarea");
      textarea.style.cssText =
        "position:absolute;resize:none;width:100%;height:100%;border:0;padding:0;margin:0;overflow-y:auto;";
      // todo: IE下只有onresize属性可用... 很纠结
      if (browser.ie && browser.version < 8) {
        textarea.style.width = holder.offsetWidth + "px";
        textarea.style.height = holder.offsetHeight + "px";
        holder.onresize = function() {
          textarea.style.width = holder.offsetWidth + "px";
          textarea.style.height = holder.offsetHeight + "px";
        };
      }
      holder.appendChild(textarea);
      return {
        setContent: function(content) {
          textarea.value = content;
        },
        getContent: function() {
          return textarea.value;
        },
        select: function() {
          var range;
          if (browser.ie) {
            range = textarea.createTextRange();
            range.collapse(true);
            range.select();
          } else {
            //todo: chrome下无法设置焦点
            textarea.setSelectionRange(0, 0);
            textarea.focus();
          }
        },
        dispose: function() {
          holder.removeChild(textarea);
          // todo
          holder.onresize = null;
          textarea = null;
          holder = null;
        },
        focus: function (){
          textarea.focus();
        },
        blur: function (){
          textarea.blur();
        }
      };
    },
    codemirror: function(editor, holder) {
      var codeEditor = window.CodeMirror(holder, {
        mode: "text/html",
        tabMode: "indent",
        lineNumbers: true,
        lineWrapping: true,
        onChange: function(v){
          editor.sync();
          // console.log('CodeMirror.onChange',v.getValue());
        }
      });
      // console.log('sourceEditor',codeEditor);
      var dom = codeEditor.getWrapperElement();
      dom.style.cssText =
        'position:absolute;left:0;top:0;width:100%;height:100%;font-family:consolas,"Courier new",monospace;font-size:13px;';
      codeEditor.getScrollerElement().style.cssText =
        "position:absolute;left:0;top:0;width:100%;height:100%;";
      codeEditor.refresh();
      return {
        getCodeMirror: function() {
          return codeEditor;
        },
        setContent: function(content) {
          codeEditor.setValue(content);
        },
        getContent: function() {
          return codeEditor.getValue();
        },
        select: function() {
          codeEditor.focus();
        },
        dispose: function() {
          holder.removeChild(dom);
          dom = null;
          codeEditor = null;
        },
        focus: function (){
          codeEditor.focus();
        },
        blur: function (){
          // codeEditor.blur();
          // since codemirror not support blur()
          codeEditor.setOption('readOnly', true);
          codeEditor.setOption('readOnly', false);
        }
      };
    }
  };

  UE.plugins["source"] = function() {
    var me = this;
    var opt = this.options;
    var sourceMode = false;
    var sourceEditor;
    var orgSetContent;
    var orgFocus;
    var orgBlur;
    opt.sourceEditor = browser.ie
      ? "textarea"
      : opt.sourceEditor || "codemirror";

    me.setOpt({
      sourceEditorFirst: false
    });
    function createSourceEditor(holder) {
      return sourceEditors[
        opt.sourceEditor == "codemirror" && window.CodeMirror
          ? "codemirror"
          : "textarea"
      ](me, holder);
    }

    var bakCssText;
    //解决在源码模式下getContent不能得到最新的内容问题
    var oldGetContent, bakAddress;

    /**
         * 切换源码模式和编辑模式
         * @command source
         * @method execCommand
         * @param { String } cmd 命令字符串
         * @example
         * ```javascript
         * editor.execCommand( 'source');
         * ```
         */

    /**
         * 查询当前编辑区域的状态是源码模式还是可视化模式
         * @command source
         * @method queryCommandState
         * @param { String } cmd 命令字符串
         * @return { int } 如果当前是源码编辑模式，返回1，否则返回0
         * @example
         * ```javascript
         * editor.queryCommandState( 'source' );
         * ```
         */

    me.commands["source"] = {
      execCommand: function() {
        sourceMode = !sourceMode;
        if (sourceMode) {
          bakAddress = me.selection.getRange().createAddress(false, true);
          me.undoManger && me.undoManger.save(true);
          if (browser.gecko) {
            me.body.contentEditable = false;
          }

          bakCssText = me.iframe.style.cssText;
          me.iframe.style.cssText +=
            "position:absolute;left:-32768px;top:-32768px;";

          me.fireEvent("beforegetcontent");
          var root = UE.htmlparser(me.body.innerHTML);
          me.filterOutputRule(root);
          root.traversal(function(node) {
            if (node.type == "element") {
              switch (node.tagName) {
                case "td":
                case "th":
                case "caption":
                  if (node.children && node.children.length == 1) {
                    if (node.firstChild().tagName == "br") {
                      node.removeChild(node.firstChild());
                    }
                  }
                  break;
                case "pre":
                  node.innerText(node.innerText().replace(/&nbsp;/g, " "));
              }
            }
          });

          me.fireEvent("aftergetcontent");

          var content = root.toHtml(true);

          sourceEditor = createSourceEditor(me.iframe.parentNode);

          sourceEditor.setContent(content);

          orgSetContent = me.setContent;

          me.setContent = function(html) {
            //这里暂时不触发事件，防止报错
            var root = UE.htmlparser(html);
            me.filterInputRule(root);
            html = root.toHtml();
            sourceEditor.setContent(html);
          };

          setTimeout(function() {
            sourceEditor.select();
            me.addListener("fullscreenchanged", function() {
              try {
                sourceEditor.getCodeMirror().refresh();
              } catch (e) {}
            });
          });

          //重置getContent，源码模式下取值也能是最新的数据
          oldGetContent = me.getContent;
          me.getContent = function() {
            return (
              sourceEditor.getContent() ||
              "<p>" + (browser.ie ? "" : "<br/>") + "</p>"
            );
          };

          orgFocus = me.focus;
          orgBlur = me.blur;

          me.focus = function(){
            sourceEditor.focus();
          };

          me.blur = function(){
            orgBlur.call(me);
            sourceEditor.blur();
          };
        } else {
          me.iframe.style.cssText = bakCssText;
          var cont =
            sourceEditor.getContent() ||
            "<p>" + (browser.ie ? "" : "<br/>") + "</p>";
          //处理掉block节点前后的空格,有可能会误命中，暂时不考虑
          cont = cont.replace(
            new RegExp("[\\r\\t\\n ]*</?(\\w+)\\s*(?:[^>]*)>", "g"),
            function(a, b) {
              if (b && !dtd.$inlineWithA[b.toLowerCase()]) {
                return a.replace(/(^[\n\r\t ]*)|([\n\r\t ]*$)/g, "");
              }
              return a.replace(/(^[\n\r\t]*)|([\n\r\t]*$)/g, "");
            }
          );

          me.setContent = orgSetContent;

          me.setContent(cont);
          sourceEditor.dispose();
          sourceEditor = null;
          //还原getContent方法
          me.getContent = oldGetContent;

          me.focus = orgFocus;
          me.blur = orgBlur;

          var first = me.body.firstChild;
          //trace:1106 都删除空了，下边会报错，所以补充一个p占位
          if (!first) {
            me.body.innerHTML = "<p>" + (browser.ie ? "" : "<br/>") + "</p>";
            first = me.body.firstChild;
          }

          //要在ifm为显示时ff才能取到selection,否则报错
          //这里不能比较位置了
          me.undoManger && me.undoManger.save(true);

          if (browser.gecko) {
            var input = document.createElement("input");
            input.style.cssText = "position:absolute;left:0;top:-32768px";

            document.body.appendChild(input);

            me.body.contentEditable = false;
            setTimeout(function() {
              domUtils.setViewportOffset(input, { left: -32768, top: 0 });
              input.focus();
              setTimeout(function() {
                me.body.contentEditable = true;
                me.selection.getRange().moveToAddress(bakAddress).select(true);
                domUtils.remove(input);
              });
            });
          } else {
            //ie下有可能报错，比如在代码顶头的情况
            try {
              me.selection.getRange().moveToAddress(bakAddress).select(true);
            } catch (e) {}
          }
        }
        this.fireEvent("sourcemodechanged", sourceMode);
      },
      queryCommandState: function() {
        return sourceMode | 0;
      },
      notNeedUndo: 1
    };
    var oldQueryCommandState = me.queryCommandState;

    me.queryCommandState = function(cmdName) {
      cmdName = cmdName.toLowerCase();
      if (sourceMode) {
        //源码模式下可以开启的命令
        return cmdName in
          {
            source: 1,
            fullscreen: 1
          }
          ? 1
          : -1;
      }
      return oldQueryCommandState.apply(this, arguments);
    };

    if (opt.sourceEditor == "codemirror") {
      me.addListener("ready", function() {
        utils.loadFile(
          document,
          {
            src:
              opt.codeMirrorJsUrl ||
                opt.UEDITOR_HOME_URL + "third-party/codemirror/codemirror.js",
            tag: "script",
            type: "text/javascript",
            defer: "defer"
          },
          function() {
            if (opt.sourceEditorFirst) {
              setTimeout(function() {
                me.execCommand("source");
              }, 0);
            }
          }
        );
        utils.loadFile(document, {
          tag: "link",
          rel: "stylesheet",
          type: "text/css",
          href:
            opt.codeMirrorCssUrl ||
              opt.UEDITOR_HOME_URL + "third-party/codemirror/codemirror.css?221123"
        });
      });
    }
  };
})();


// plugins/enterkey.js
///import core
///import plugins/undo.js
///commands 设置回车标签p或br
///commandsName  EnterKey
///commandsTitle  设置回车标签p或br
/**
 * @description 处理回车
 * @author zhanyi
 */
UE.plugins["enterkey"] = function() {
  var hTag,
    me = this,
    tag = me.options.enterTag;
  me.addListener("keyup", function(type, evt) {
    var keyCode = evt.keyCode || evt.which;
    if (keyCode == 13) {
      var range = me.selection.getRange(),
        start = range.startContainer,
        doSave;

      //修正在h1-h6里边回车后不能嵌套p的问题
      if (!browser.ie) {
        if (/h\d/i.test(hTag)) {
          if (browser.gecko) {
            var h = domUtils.findParentByTagName(
              start,
              [
                "h1",
                "h2",
                "h3",
                "h4",
                "h5",
                "h6",
                "blockquote",
                "caption",
                "table"
              ],
              true
            );
            if (!h) {
              me.document.execCommand("formatBlock", false, "<p>");
              doSave = 1;
            }
          } else {
            //chrome remove div
            if (start.nodeType == 1) {
              var tmp = me.document.createTextNode(""),
                div;
              range.insertNode(tmp);
              div = domUtils.findParentByTagName(tmp, "div", true);
              if (div) {
                var p = me.document.createElement("p");
                while (div.firstChild) {
                  p.appendChild(div.firstChild);
                }
                div.parentNode.insertBefore(p, div);
                domUtils.remove(div);
                range.setStartBefore(tmp).setCursor();
                doSave = 1;
              }
              domUtils.remove(tmp);
            }
          }

          if (me.undoManger && doSave) {
            me.undoManger.save();
          }
        }
        //没有站位符，会出现多行的问题
        browser.opera && range.select();
      } else {
        me.fireEvent("saveScene", true, true);
      }
    }
  });

  me.addListener("keydown", function(type, evt) {
    var keyCode = evt.keyCode || evt.which;
    if (keyCode == 13) {
      //回车
      if (me.fireEvent("beforeenterkeydown")) {
        domUtils.preventDefault(evt);
        return;
      }
      me.fireEvent("saveScene", true, true);
      hTag = "";

      var range = me.selection.getRange();

      if (!range.collapsed) {
        //跨td不能删
        var start = range.startContainer,
          end = range.endContainer,
          startTd = domUtils.findParentByTagName(start, "td", true),
          endTd = domUtils.findParentByTagName(end, "td", true);
        if (
          (startTd && endTd && startTd !== endTd) ||
          (!startTd && endTd) ||
          (startTd && !endTd)
        ) {
          evt.preventDefault ? evt.preventDefault() : (evt.returnValue = false);
          return;
        }
      }
      if (tag == "p") {
        if (!browser.ie) {
          start = domUtils.findParentByTagName(
            range.startContainer,
            [
              "ol",
              "ul",
              "p",
              "h1",
              "h2",
              "h3",
              "h4",
              "h5",
              "h6",
              "blockquote",
              "caption"
            ],
            true
          );

          //opera下执行formatblock会在table的场景下有问题，回车在opera原生支持很好，所以暂时在opera去掉调用这个原生的command
          //trace:2431
          if (!start && !browser.opera) {
            me.document.execCommand("formatBlock", false, "<p>");

            if (browser.gecko) {
              range = me.selection.getRange();
              start = domUtils.findParentByTagName(
                range.startContainer,
                "p",
                true
              );
              start && domUtils.removeDirtyAttr(start);
            }
          } else {
            hTag = start.tagName;
            start.tagName.toLowerCase() == "p" &&
              browser.gecko &&
              domUtils.removeDirtyAttr(start);
          }
        }
      } else {
        evt.preventDefault ? evt.preventDefault() : (evt.returnValue = false);

        if (!range.collapsed) {
          range.deleteContents();
          start = range.startContainer;
          if (
            start.nodeType == 1 &&
            (start = start.childNodes[range.startOffset])
          ) {
            while (start.nodeType == 1) {
              if (dtd.$empty[start.tagName]) {
                range.setStartBefore(start).setCursor();
                if (me.undoManger) {
                  me.undoManger.save();
                }
                return false;
              }
              if (!start.firstChild) {
                var br = range.document.createElement("br");
                start.appendChild(br);
                range.setStart(start, 0).setCursor();
                if (me.undoManger) {
                  me.undoManger.save();
                }
                return false;
              }
              start = start.firstChild;
            }
            if (start === range.startContainer.childNodes[range.startOffset]) {
              br = range.document.createElement("br");
              range.insertNode(br).setCursor();
            } else {
              range.setStart(start, 0).setCursor();
            }
          } else {
            br = range.document.createElement("br");
            range.insertNode(br).setStartAfter(br).setCursor();
          }
        } else {
          br = range.document.createElement("br");
          range.insertNode(br);
          var parent = br.parentNode;
          if (parent.lastChild === br) {
            br.parentNode.insertBefore(br.cloneNode(true), br);
            range.setStartBefore(br);
          } else {
            range.setStartAfter(br);
          }
          range.setCursor();
        }
      }
    }
  });
};


// plugins/keystrokes.js
/* 处理特殊键的兼容性问题 */
UE.plugins["keystrokes"] = function() {
  var me = this;
  var collapsed = true;
  me.addListener("keydown", function(type, evt) {
    var keyCode = evt.keyCode || evt.which,
      rng = me.selection.getRange();

    //处理全选的情况
    if (
      !rng.collapsed &&
      !(evt.ctrlKey || evt.shiftKey || evt.altKey || evt.metaKey) &&
      ((keyCode >= 65 && keyCode <= 90) ||
        (keyCode >= 48 && keyCode <= 57) ||
        (keyCode >= 96 && keyCode <= 111) ||
        {
          13: 1,
          8: 1,
          46: 1
        }[keyCode])
    ) {
      var tmpNode = rng.startContainer;
      if (domUtils.isFillChar(tmpNode)) {
        rng.setStartBefore(tmpNode);
      }
      tmpNode = rng.endContainer;
      if (domUtils.isFillChar(tmpNode)) {
        rng.setEndAfter(tmpNode);
      }
      rng.txtToElmBoundary();
      //结束边界可能放到了br的前边，要把br包含进来
      // x[xxx]<br/>
      if (rng.endContainer && rng.endContainer.nodeType == 1) {
        tmpNode = rng.endContainer.childNodes[rng.endOffset];
        if (tmpNode && domUtils.isBr(tmpNode)) {
          rng.setEndAfter(tmpNode);
        }
      }
      if (rng.startOffset == 0) {
        tmpNode = rng.startContainer;
        if (domUtils.isBoundaryNode(tmpNode, "firstChild")) {
          tmpNode = rng.endContainer;
          if (
            rng.endOffset ==
              (tmpNode.nodeType == 3
                ? tmpNode.nodeValue.length
                : tmpNode.childNodes.length) &&
            domUtils.isBoundaryNode(tmpNode, "lastChild")
          ) {
            me.fireEvent("saveScene");
            me.body.innerHTML = "<p>" + (browser.ie ? "" : "<br/>") + "</p>";
            rng.setStart(me.body.firstChild, 0).setCursor(false, true);
            me._selectionChange();
            return;
          }
        }
      }
    }

    //处理backspace
    if (keyCode == keymap.Backspace) {
      rng = me.selection.getRange();
      collapsed = rng.collapsed;
      if (me.fireEvent("delkeydown", evt)) {
        return;
      }
      var start, end;
      //避免按两次删除才能生效的问题
      if (rng.collapsed && rng.inFillChar()) {
        start = rng.startContainer;

        if (domUtils.isFillChar(start)) {
          rng.setStartBefore(start).shrinkBoundary(true).collapse(true);
          domUtils.remove(start);
        } else {
          start.nodeValue = start.nodeValue.replace(
            new RegExp("^" + domUtils.fillChar),
            ""
          );
          rng.startOffset--;
          rng.collapse(true).select(true);
        }
      }

      //解决选中control元素不能删除的问题
      if ((start = rng.getClosedNode())) {
        me.fireEvent("saveScene");
        rng.setStartBefore(start);
        domUtils.remove(start);
        rng.setCursor();
        me.fireEvent("saveScene");
        domUtils.preventDefault(evt);
        return;
      }
      //阻止在table上的删除
      if (!browser.ie) {
        start = domUtils.findParentByTagName(rng.startContainer, "table", true);
        end = domUtils.findParentByTagName(rng.endContainer, "table", true);
        if ((start && !end) || (!start && end) || start !== end) {
          evt.preventDefault();
          return;
        }
      }
    }
    //处理tab键的逻辑
    if (keyCode == keymap.Tab) {
      //不处理以下标签
      var excludeTagNameForTabKey = {
        ol: 1,
        ul: 1,
        table: 1
      };
      //处理组件里的tab按下事件
      if (me.fireEvent("tabkeydown", evt)) {
        domUtils.preventDefault(evt);
        return;
      }
      var range = me.selection.getRange();
      me.fireEvent("saveScene");
      for (
        var i = 0,
          txt = "",
          tabSize = me.options.tabSize || 4,
          tabNode = me.options.tabNode || "&nbsp;";
        i < tabSize;
        i++
      ) {
        txt += tabNode;
      }
      var span = me.document.createElement("span");
      span.innerHTML = txt + domUtils.fillChar;
      if (range.collapsed) {
        range.insertNode(span.cloneNode(true).firstChild).setCursor(true);
      } else {
        var filterFn = function(node) {
          return (
            domUtils.isBlockElm(node) &&
            !excludeTagNameForTabKey[node.tagName.toLowerCase()]
          );
        };
        //普通的情况
        start = domUtils.findParent(range.startContainer, filterFn, true);
        end = domUtils.findParent(range.endContainer, filterFn, true);
        if (start && end && start === end) {
          range.deleteContents();
          range.insertNode(span.cloneNode(true).firstChild).setCursor(true);
        } else {
          var bookmark = range.createBookmark();
          range.enlarge(true);
          var bookmark2 = range.createBookmark(),
            current = domUtils.getNextDomNode(bookmark2.start, false, filterFn);
          while (
            current &&
            !(
              domUtils.getPosition(current, bookmark2.end) &
              domUtils.POSITION_FOLLOWING
            )
          ) {
            current.insertBefore(
              span.cloneNode(true).firstChild,
              current.firstChild
            );
            current = domUtils.getNextDomNode(current, false, filterFn);
          }
          range.moveToBookmark(bookmark2).moveToBookmark(bookmark).select();
        }
      }
      domUtils.preventDefault(evt);
    }
    //trace:1634
    //ff的del键在容器空的时候，也会删除
    if (browser.gecko && keyCode == 46) {
      range = me.selection.getRange();
      if (range.collapsed) {
        start = range.startContainer;
        if (domUtils.isEmptyBlock(start)) {
          var parent = start.parentNode;
          while (
            domUtils.getChildCount(parent) == 1 &&
            !domUtils.isBody(parent)
          ) {
            start = parent;
            parent = parent.parentNode;
          }
          if (start === parent.lastChild) evt.preventDefault();
          return;
        }
      }
    }

    /* 修复在编辑区域快捷键 (Mac:meta+alt+I; Win:ctrl+shift+I) 打不开 chrome 控制台的问题 */
    browser.chrome &&
      me.on("keydown", function(type, e) {
        var keyCode = e.keyCode || e.which;
        if (
          ((e.metaKey && e.altKey) || (e.ctrlKey && e.shiftKey)) &&
          keyCode == 73
        ) {
          return true;
        }
      });
  });
  me.addListener("keyup", function(type, evt) {
    var keyCode = evt.keyCode || evt.which,
      rng,
      me = this;
    if (keyCode == keymap.Backspace) {
      if (me.fireEvent("delkeyup")) {
        return;
      }
      rng = me.selection.getRange();
      if (rng.collapsed) {
        var tmpNode,
          autoClearTagName = ["h1", "h2", "h3", "h4", "h5", "h6"];
        if (
          (tmpNode = domUtils.findParentByTagName(
            rng.startContainer,
            autoClearTagName,
            true
          ))
        ) {
          if (domUtils.isEmptyBlock(tmpNode)) {
            var pre = tmpNode.previousSibling;
            if (pre && pre.nodeName != "TABLE") {
              domUtils.remove(tmpNode);
              rng.setStartAtLast(pre).setCursor(false, true);
              return;
            } else {
              var next = tmpNode.nextSibling;
              if (next && next.nodeName != "TABLE") {
                domUtils.remove(tmpNode);
                rng.setStartAtFirst(next).setCursor(false, true);
                return;
              }
            }
          }
        }
        //处理当删除到body时，要重新给p标签展位
        if (domUtils.isBody(rng.startContainer)) {
          var tmpNode = domUtils.createElement(me.document, "p", {
            innerHTML: browser.ie ? domUtils.fillChar : "<br/>"
          });
          rng.insertNode(tmpNode).setStart(tmpNode, 0).setCursor(false, true);
        }
      }

      //chrome下如果删除了inline标签，浏览器会有记忆，在输入文字还是会套上刚才删除的标签，所以这里再选一次就不会了
      if (
        !collapsed &&
        (rng.startContainer.nodeType == 3 ||
          (rng.startContainer.nodeType == 1 &&
            domUtils.isEmptyBlock(rng.startContainer)))
      ) {
        if (browser.ie) {
          var span = rng.document.createElement("span");
          rng.insertNode(span).setStartBefore(span).collapse(true);
          rng.select();
          domUtils.remove(span);
        } else {
          rng.select();
        }
      }
    }
  });
};


// plugins/fiximgclick.js
///import core
///commands 修复chrome下图片不能点击的问题，出现八个角可改变大小
///commandsName  FixImgClick
///commandsTitle  修复chrome下图片不能点击的问题，出现八个角可改变大小
//修复chrome下图片不能点击的问题，出现八个角可改变大小

UE.plugins["fiximgclick"] = (function() {
  var elementUpdated = false;
  function Scale() {
    this.editor = null;
    this.resizer = null;
    this.cover = null;
    this.doc = document;
    this.prePos = { x: 0, y: 0 };
    this.startPos = { x: 0, y: 0 };
  }

  (function() {
    var rect = [
      //[left, top, width, height]
      [0, 0, -1, -1],
      [0, 0, 0, -1],
      [0, 0, 1, -1],
      [0, 0, -1, 0],
      [0, 0, 1, 0],
      [0, 0, -1, 1],
      [0, 0, 0, 1],
      [0, 0, 1, 1]
    ];

    Scale.prototype = {
      init: function(editor) {
        var me = this;
        me.editor = editor;
        me.startPos = this.prePos = { x: 0, y: 0 };
        me.dragId = -1;

        var hands = [],
          cover = (me.cover = document.createElement("div")),
          resizer = (me.resizer = document.createElement("div"));

        cover.id = me.editor.ui.id + "_imagescale_cover";
        cover.style.cssText =
          "position:absolute;display:none;z-index:" +
          me.editor.options.zIndex +
          ";filter:alpha(opacity=0); opacity:0;background:#CCC;";
        domUtils.on(cover, "mousedown click", function() {
          me.hide();
        });

        for (i = 0; i < 8; i++) {
          hands.push(
            '<span class="edui-editor-imagescale-hand' + i + '"></span>'
          );
        }
        resizer.id = me.editor.ui.id + "_imagescale";
        resizer.className = "edui-editor-imagescale";
        resizer.innerHTML = hands.join("");
        resizer.style.cssText +=
          ";display:none;border:1px solid #3b77ff;z-index:" +
          me.editor.options.zIndex +
          ";";

        me.editor.ui.getDom().appendChild(cover);
        me.editor.ui.getDom().appendChild(resizer);

        me.initStyle();
        me.initEvents();
      },
      initStyle: function() {
        utils.cssRule(
          "imagescale",
          ".edui-editor-imagescale{display:none;position:absolute;border:1px solid #38B2CE;cursor:hand;-webkit-box-sizing: content-box;-moz-box-sizing: content-box;box-sizing: content-box;}" +
            ".edui-editor-imagescale span{position:absolute;width:6px;height:6px;overflow:hidden;font-size:0px;display:block;background-color:#3C9DD0;}" +
            ".edui-editor-imagescale .edui-editor-imagescale-hand0{cursor:nw-resize;top:0;margin-top:-4px;left:0;margin-left:-4px;}" +
            ".edui-editor-imagescale .edui-editor-imagescale-hand1{cursor:n-resize;top:0;margin-top:-4px;left:50%;margin-left:-4px;}" +
            ".edui-editor-imagescale .edui-editor-imagescale-hand2{cursor:ne-resize;top:0;margin-top:-4px;left:100%;margin-left:-3px;}" +
            ".edui-editor-imagescale .edui-editor-imagescale-hand3{cursor:w-resize;top:50%;margin-top:-4px;left:0;margin-left:-4px;}" +
            ".edui-editor-imagescale .edui-editor-imagescale-hand4{cursor:e-resize;top:50%;margin-top:-4px;left:100%;margin-left:-3px;}" +
            ".edui-editor-imagescale .edui-editor-imagescale-hand5{cursor:sw-resize;top:100%;margin-top:-3px;left:0;margin-left:-4px;}" +
            ".edui-editor-imagescale .edui-editor-imagescale-hand6{cursor:s-resize;top:100%;margin-top:-3px;left:50%;margin-left:-4px;}" +
            ".edui-editor-imagescale .edui-editor-imagescale-hand7{cursor:se-resize;top:100%;margin-top:-3px;left:100%;margin-left:-3px;}"
        );
      },
      initEvents: function() {
        var me = this;

        me.startPos.x = me.startPos.y = 0;
        me.isDraging = false;
      },
      _eventHandler: function(e) {
        var me = this;
        switch (e.type) {
          case "mousedown":
            var hand = e.target || e.srcElement,
              hand;
            if (
              hand.className.indexOf("edui-editor-imagescale-hand") != -1 &&
              me.dragId == -1
            ) {
              me.dragId = hand.className.slice(-1);
              me.startPos.x = me.prePos.x = e.clientX;
              me.startPos.y = me.prePos.y = e.clientY;
              domUtils.on(me.doc, "mousemove", me.proxy(me._eventHandler, me));
            }
            break;
          case "mousemove":
            if (me.dragId != -1) {
              me.updateContainerStyle(me.dragId, {
                x: e.clientX - me.prePos.x,
                y: e.clientY - me.prePos.y
              });
              me.prePos.x = e.clientX;
              me.prePos.y = e.clientY;
              elementUpdated = true;
              me.updateTargetElement();
            }
            break;
          case "mouseup":
            if (me.dragId != -1) {
              me.updateContainerStyle(me.dragId, {
                x: e.clientX - me.prePos.x,
                y: e.clientY - me.prePos.y
              });
              me.updateTargetElement();
              if (me.target.parentNode) me.attachTo(me.target);
              me.dragId = -1;
            }
            domUtils.un(me.doc, "mousemove", me.proxy(me._eventHandler, me));
            //修复只是点击挪动点，但没有改变大小，不应该触发contentchange
            if (elementUpdated) {
              elementUpdated = false;
              me.editor.fireEvent("contentchange");
            }

            break;
          default:
            break;
        }
      },
      updateTargetElement: function() {
        var me = this;
        domUtils.setStyles(me.target, {
          width: me.resizer.style.width,
          height: me.resizer.style.height
        });
        me.target.width = parseInt(me.resizer.style.width);
        me.target.height = parseInt(me.resizer.style.height);
        me.attachTo(me.target);
      },
      updateContainerStyle: function(dir, offset) {
        var me = this,
          dom = me.resizer,
          tmp;

        if (rect[dir][0] != 0) {
          tmp = parseInt(dom.style.left) + offset.x;
          dom.style.left = me._validScaledProp("left", tmp) + "px";
        }
        if (rect[dir][1] != 0) {
          tmp = parseInt(dom.style.top) + offset.y;
          dom.style.top = me._validScaledProp("top", tmp) + "px";
        }
        if (rect[dir][2] != 0) {
          tmp = dom.clientWidth + rect[dir][2] * offset.x;
          dom.style.width = me._validScaledProp("width", tmp) + "px";
        }
        if (rect[dir][3] != 0) {
          tmp = dom.clientHeight + rect[dir][3] * offset.y;
          dom.style.height = me._validScaledProp("height", tmp) + "px";
        }
      },
      _validScaledProp: function(prop, value) {
        var ele = this.resizer,
          wrap = document;

        value = isNaN(value) ? 0 : value;
        switch (prop) {
          case "left":
            return value < 0
              ? 0
              : value + ele.clientWidth > wrap.clientWidth
                ? wrap.clientWidth - ele.clientWidth
                : value;
          case "top":
            return value < 0
              ? 0
              : value + ele.clientHeight > wrap.clientHeight
                ? wrap.clientHeight - ele.clientHeight
                : value;
          case "width":
            return value <= 0
              ? 1
              : value + ele.offsetLeft > wrap.clientWidth
                ? wrap.clientWidth - ele.offsetLeft
                : value;
          case "height":
            return value <= 0
              ? 1
              : value + ele.offsetTop > wrap.clientHeight
                ? wrap.clientHeight - ele.offsetTop
                : value;
        }
      },
      hideCover: function() {
        this.cover.style.display = "none";
      },
      showCover: function() {
        var me = this,
          editorPos = domUtils.getXY(me.editor.ui.getDom()),
          iframePos = domUtils.getXY(me.editor.iframe);

        domUtils.setStyles(me.cover, {
          width: me.editor.iframe.offsetWidth + "px",
          height: me.editor.iframe.offsetHeight + "px",
          top: iframePos.y - editorPos.y + "px",
          left: iframePos.x - editorPos.x + "px",
          position: "absolute",
          display: ""
        });
      },
      show: function(targetObj) {
        var me = this;
        me.resizer.style.display = "block";
        if (targetObj) me.attachTo(targetObj);

        domUtils.on(this.resizer, "mousedown", me.proxy(me._eventHandler, me));
        domUtils.on(me.doc, "mouseup", me.proxy(me._eventHandler, me));

        me.showCover();
        me.editor.fireEvent("afterscaleshow", me);
        me.editor.fireEvent("saveScene");
      },
      hide: function() {
        var me = this;
        me.hideCover();
        me.resizer.style.display = "none";

        domUtils.un(me.resizer, "mousedown", me.proxy(me._eventHandler, me));
        domUtils.un(me.doc, "mouseup", me.proxy(me._eventHandler, me));
        me.editor.fireEvent("afterscalehide", me);
      },
      proxy: function(fn, context) {
        return function(e) {
          return fn.apply(context || this, arguments);
        };
      },
      attachTo: function(targetObj) {
        var me = this,
          target = (me.target = targetObj),
          resizer = this.resizer,
          imgPos = domUtils.getXY(target),
          iframePos = domUtils.getXY(me.editor.iframe),
          editorPos = domUtils.getXY(resizer.parentNode);

        domUtils.setStyles(resizer, {
          width: target.width + "px",
          height: target.height + "px",
          left:
            iframePos.x +
              imgPos.x -
              me.editor.document.body.scrollLeft -
              editorPos.x -
              parseInt(resizer.style.borderLeftWidth) +
              "px",
          top:
            iframePos.y +
              imgPos.y -
              me.editor.document.body.scrollTop -
              editorPos.y -
              parseInt(resizer.style.borderTopWidth) +
              "px"
        });
      }
    };
  })();

  return function() {
    var me = this,
      imageScale;

    me.setOpt("imageScaleEnabled", true);

    if (!browser.ie && me.options.imageScaleEnabled) {
      me.addListener("click", function(type, e) {
        var range = me.selection.getRange(),
          img = range.getClosedNode();

        if (img && img.tagName == "IMG" && me.body.contentEditable != "false") {
          if (
            img.getAttribute("anchorname") ||
            domUtils.hasClass(img, "loadingclass") ||
            domUtils.hasClass(img, "loaderrorclass")
          ) {
            return;
          }

          if (!imageScale) {
            imageScale = new Scale();
            imageScale.init(me);
            me.ui.getDom().appendChild(imageScale.resizer);

            var _keyDownHandler = function(e) {
              imageScale.hide();
              if (imageScale.target)
                me.selection.getRange().selectNode(imageScale.target).select();
            },
              _mouseDownHandler = function(e) {
                var ele = e.target || e.srcElement;
                if (
                  ele &&
                  (ele.className === undefined ||
                    ele.className.indexOf("edui-editor-imagescale") == -1)
                ) {
                  _keyDownHandler(e);
                }
              },
              timer;

            me.addListener("afterscaleshow", function(e) {
              me.addListener("beforekeydown", _keyDownHandler);
              me.addListener("beforemousedown", _mouseDownHandler);
              domUtils.on(document, "keydown", _keyDownHandler);
              domUtils.on(document, "mousedown", _mouseDownHandler);
              me.selection.getNative().removeAllRanges();
            });
            me.addListener("afterscalehide", function(e) {
              me.removeListener("beforekeydown", _keyDownHandler);
              me.removeListener("beforemousedown", _mouseDownHandler);
              domUtils.un(document, "keydown", _keyDownHandler);
              domUtils.un(document, "mousedown", _mouseDownHandler);
              var target = imageScale.target;
              if (target.parentNode) {
                me.selection.getRange().selectNode(target).select();
              }
            });
            //TODO 有iframe的情况，mousedown不能往下传。。
            domUtils.on(imageScale.resizer, "mousedown", function(e) {
              me.selection.getNative().removeAllRanges();
              var ele = e.target || e.srcElement;
              if (
                ele &&
                ele.className.indexOf("edui-editor-imagescale-hand") == -1
              ) {
                timer = setTimeout(function() {
                  imageScale.hide();
                  if (imageScale.target)
                    me.selection.getRange().selectNode(ele).select();
                }, 200);
              }
            });
            domUtils.on(imageScale.resizer, "mouseup", function(e) {
              var ele = e.target || e.srcElement;
              if (
                ele &&
                ele.className.indexOf("edui-editor-imagescale-hand") == -1
              ) {
                clearTimeout(timer);
              }
            });
          }
          imageScale.show(img);
        } else {
          if (imageScale && imageScale.resizer.style.display != "none")
            imageScale.hide();
        }
      });
    }

    if (browser.webkit) {
      me.addListener("click", function(type, e) {
        if (e.target.tagName == "IMG" && me.body.contentEditable != "false") {
          var range = new dom.Range(me.document);
          range.selectNode(e.target).select();
        }
      });
    }
  };
})();


// plugins/autolink.js
///import core
///commands 为非ie浏览器自动添加a标签
///commandsName  AutoLink
///commandsTitle  自动增加链接
/**
 * @description 为非ie浏览器自动添加a标签
 * @author zhanyi
 */

UE.plugin.register(
  "autolink",
  function() {
    var cont = 0;

    return !browser.ie
      ? {
          bindEvents: {
            reset: function() {
              cont = 0;
            },
            keydown: function(type, evt) {
              var me = this;
              var keyCode = evt.keyCode || evt.which;

              if (keyCode == 32 || keyCode == 13) {
                var sel = me.selection.getNative(),
                  range = sel.getRangeAt(0).cloneRange(),
                  offset,
                  charCode;

                var start = range.startContainer;
                while (start.nodeType == 1 && range.startOffset > 0) {
                  start =
                    range.startContainer.childNodes[range.startOffset - 1];
                  if (!start) {
                    break;
                  }
                  range.setStart(
                    start,
                    start.nodeType == 1
                      ? start.childNodes.length
                      : start.nodeValue.length
                  );
                  range.collapse(true);
                  start = range.startContainer;
                }

                do {
                  if (range.startOffset == 0) {
                    start = range.startContainer.previousSibling;

                    while (start && start.nodeType == 1) {
                      start = start.lastChild;
                    }
                    if (!start || domUtils.isFillChar(start)) {
                      break;
                    }
                    offset = start.nodeValue.length;
                  } else {
                    start = range.startContainer;
                    offset = range.startOffset;
                  }
                  range.setStart(start, offset - 1);
                  charCode = range.toString().charCodeAt(0);
                } while (charCode != 160 && charCode != 32);

                if (
                  range
                    .toString()
                    .replace(new RegExp(domUtils.fillChar, "g"), "")
                    .match(/(?:https?:\/\/|ssh:\/\/|ftp:\/\/|file:\/|www\.)/i)
                ) {
                  while (range.toString().length) {
                    if (
                      /^(?:https?:\/\/|ssh:\/\/|ftp:\/\/|file:\/|www\.)/i.test(
                        range.toString()
                      )
                    ) {
                      break;
                    }
                    try {
                      range.setStart(
                        range.startContainer,
                        range.startOffset + 1
                      );
                    } catch (e) {
                      //trace:2121
                      var start = range.startContainer;
                      while (!(next = start.nextSibling)) {
                        if (domUtils.isBody(start)) {
                          return;
                        }
                        start = start.parentNode;
                      }
                      range.setStart(next, 0);
                    }
                  }
                  //range的开始边界已经在a标签里的不再处理
                  if (
                    domUtils.findParentByTagName(
                      range.startContainer,
                      "a",
                      true
                    )
                  ) {
                    return;
                  }
                  var a = me.document.createElement("a"),
                    text = me.document.createTextNode(" "),
                    href;

                  me.undoManger && me.undoManger.save();
                  a.appendChild(range.extractContents());
                  a.href = a.innerHTML = a.innerHTML.replace(/<[^>]+>/g, "");
                  href = a
                    .getAttribute("href")
                    .replace(new RegExp(domUtils.fillChar, "g"), "");
                  href = /^(?:https?:\/\/)/gi.test(href)
                    ? href
                    : "http://" + href;
                  a.setAttribute("_src", utils.html(href));
                  a.href = utils.html(href);

                  range.insertNode(a);
                  a.parentNode.insertBefore(text, a.nextSibling);
                  range.setStart(text, 0);
                  range.collapse(true);
                  sel.removeAllRanges();
                  sel.addRange(range);
                  me.undoManger && me.undoManger.save();
                }
              }
            }
          }
        }
      : {};
  },
  function() {
    var keyCodes = {
      37: 1,
      38: 1,
      39: 1,
      40: 1,
      13: 1,
      32: 1
    };
    function checkIsCludeLink(node) {
      if (node.nodeType == 3) {
        return null;
      }
      if (node.nodeName == "A") {
        return node;
      }
      var lastChild = node.lastChild;

      while (lastChild) {
        if (lastChild.nodeName == "A") {
          return lastChild;
        }
        if (lastChild.nodeType == 3) {
          if (domUtils.isWhitespace(lastChild)) {
            lastChild = lastChild.previousSibling;
            continue;
          }
          return null;
        }
        lastChild = lastChild.lastChild;
      }
    }
    browser.ie &&
      this.addListener("keyup", function(cmd, evt) {
        var me = this,
          keyCode = evt.keyCode;
        if (keyCodes[keyCode]) {
          var rng = me.selection.getRange();
          var start = rng.startContainer;

          if (keyCode == 13) {
            while (
              start &&
              !domUtils.isBody(start) &&
              !domUtils.isBlockElm(start)
            ) {
              start = start.parentNode;
            }
            if (start && !domUtils.isBody(start) && start.nodeName == "P") {
              var pre = start.previousSibling;
              if (pre && pre.nodeType == 1) {
                var pre = checkIsCludeLink(pre);
                if (pre && !pre.getAttribute("_href")) {
                  domUtils.remove(pre, true);
                }
              }
            }
          } else if (keyCode == 32) {
            if (start.nodeType == 3 && /^\s$/.test(start.nodeValue)) {
              start = start.previousSibling;
              if (
                start &&
                start.nodeName == "A" &&
                !start.getAttribute("_href")
              ) {
                domUtils.remove(start, true);
              }
            }
          } else {
            start = domUtils.findParentByTagName(start, "a", true);
            if (start && !start.getAttribute("_href")) {
              var bk = rng.createBookmark();

              domUtils.remove(start, true);
              rng.moveToBookmark(bk).select(true);
            }
          }
        }
      });
  }
);


// plugins/autoheight.js
///import core
///commands 当输入内容超过编辑器高度时，编辑器自动增高
///commandsName  AutoHeight,autoHeightEnabled
///commandsTitle  自动增高
/**
 * @description 自动伸展
 * @author zhanyi
 */
UE.plugins["autoheight"] = function() {
  var me = this;
  //提供开关，就算加载也可以关闭
  me.autoHeightEnabled = me.options.autoHeightEnabled !== false;
  if (!me.autoHeightEnabled) {
    return;
  }

  var bakOverflow,
    lastHeight = 0,
    options = me.options,
    currentHeight,
    timer;

  function adjustHeight() {
    var me = this;
    clearTimeout(timer);
    if (isFullscreen) return;
    if (
      !me.queryCommandState ||
      (me.queryCommandState && me.queryCommandState("source") != 1)
    ) {
      timer = setTimeout(function() {
        var node = me.body.lastChild;
        while (node && node.nodeType != 1) {
          node = node.previousSibling;
        }
        if (node && node.nodeType == 1) {
          node.style.clear = "both";
          currentHeight = Math.max(
            domUtils.getXY(node).y + node.offsetHeight + 25,
            Math.max(options.minFrameHeight, options.initialFrameHeight)
          );
          if (currentHeight !== lastHeight) {
            me.iframe.parentNode.style.transition = 'width 0.3s, height 0.3s, easy-in-out';
            if (currentHeight !== parseInt(me.iframe.parentNode.style.height)) {
              me.iframe.parentNode.style.height = currentHeight + "px";
            }
            me.body.style.height = currentHeight + "px";
            lastHeight = currentHeight;
          }
          domUtils.removeStyle(node, "clear");
        }
      }, 50);
    }
  }
  var isFullscreen;
  me.addListener("fullscreenchanged", function(cmd, f) {
    isFullscreen = f;
  });
  me.addListener("destroy", function() {
    domUtils.un(me.window, "scroll", fixedScrollTop);
    me.removeListener(
      "contentchange afterinserthtml keyup mouseup",
      adjustHeight
    );
  });
  me.enableAutoHeight = function() {
    var me = this;
    if (!me.autoHeightEnabled) {
      return;
    }
    var doc = me.document;
    me.autoHeightEnabled = true;
    bakOverflow = doc.body.style.overflowY;
    doc.body.style.overflowY = "hidden";
    me.addListener("contentchange afterinserthtml keyup mouseup", adjustHeight);
    //ff不给事件算得不对

    setTimeout(function() {
      adjustHeight.call(me);
    }, browser.gecko ? 100 : 0);
    me.fireEvent("autoheightchanged", me.autoHeightEnabled);
  };
  me.disableAutoHeight = function() {
    me.body.style.overflowY = bakOverflow || "";

    me.removeListener("contentchange", adjustHeight);
    me.removeListener("keyup", adjustHeight);
    me.removeListener("mouseup", adjustHeight);
    me.autoHeightEnabled = false;
    me.fireEvent("autoheightchanged", me.autoHeightEnabled);
  };

  me.on("setHeight", function() {
    me.disableAutoHeight();
  });
  me.addListener("ready", function() {
    me.enableAutoHeight();
    //trace:1764
    var timer;
    domUtils.on(
      browser.ie ? me.body : me.document,
      browser.webkit ? "dragover" : "drop",
      function() {
        clearTimeout(timer);
        timer = setTimeout(function() {
          //trace:3681
          adjustHeight.call(me);
        }, 100);
      }
    );
    //修复内容过多时，回到顶部，顶部内容被工具栏遮挡问题
    domUtils.on(me.window, "scroll", fixedScrollTop);
  });

  var lastScrollY;

  function fixedScrollTop() {
    if (!me.window) return;
    if (lastScrollY === null) {
      lastScrollY = me.window.scrollY;
    } else if (me.window.scrollY == 0 && lastScrollY != 0) {
      me.window.scrollTo(0, 0);
      lastScrollY = null;
    }
  }
};


// plugins/autofloat.js
///import core
///commands 悬浮工具栏
///commandsName  AutoFloat,autoFloatEnabled
///commandsTitle  悬浮工具栏
/**
 *  modified by chengchao01
 *  注意： 引入此功能后，在IE6下会将body的背景图片覆盖掉！
 */
UE.plugins["autofloat"] = function() {
  var me = this,
    lang = me.getLang();
  me.setOpt({
    topOffset: 0
  });
  var optsAutoFloatEnabled = me.options.autoFloatEnabled !== false,
    topOffset = me.options.topOffset;

  //如果不固定toolbar的位置，则直接退出
  if (!optsAutoFloatEnabled) {
    return;
  }
  var uiUtils = UE.ui.uiUtils,
    LteIE6 = browser.ie && browser.version <= 6,
    quirks = browser.quirks;

  function checkHasUI() {
    if (!UE.ui) {
      alert(lang.autofloatMsg);
      return 0;
    }
    return 1;
  }
  function fixIE6FixedPos() {
    var docStyle = document.body.style;
    docStyle.backgroundImage = 'url("about:blank")';
    docStyle.backgroundAttachment = "fixed";
  }
  var bakCssText,
    placeHolder = document.createElement("div"),
    toolbarBox,
    orgTop,
    getPosition,
    flag = true; //ie7模式下需要偏移
  function setFloating() {
    var toobarBoxPos = domUtils.getXY(toolbarBox),
      origalFloat = domUtils.getComputedStyle(toolbarBox, "position"),
      origalLeft = domUtils.getComputedStyle(toolbarBox, "left");
    toolbarBox.style.width = toolbarBox.offsetWidth + "px";
    toolbarBox.style.zIndex = me.options.zIndex * 1 + 1;
    toolbarBox.parentNode.insertBefore(placeHolder, toolbarBox);
    if (LteIE6 || (quirks && browser.ie)) {
      if (toolbarBox.style.position != "absolute") {
        toolbarBox.style.position = "absolute";
      }
      toolbarBox.style.top =
        (document.body.scrollTop || document.documentElement.scrollTop) -
        orgTop +
        topOffset +
        "px";
    } else {
      if (browser.ie7Compat && flag) {
        flag = false;
        toolbarBox.style.left =
          domUtils.getXY(toolbarBox).x -
          document.documentElement.getBoundingClientRect().left +
          2 +
          "px";
      }
      if (toolbarBox.style.position != "fixed") {
        toolbarBox.style.position = "fixed";
        toolbarBox.style.top = topOffset + "px";
        (origalFloat == "absolute" || origalFloat == "relative") &&
          parseFloat(origalLeft) &&
          (toolbarBox.style.left = toobarBoxPos.x + "px");
      }
    }
  }
  function unsetFloating() {
    flag = true;
    if (placeHolder.parentNode) {
      placeHolder.parentNode.removeChild(placeHolder);
    }

    toolbarBox.style.cssText = bakCssText;
  }

  me.unsetFloating = unsetFloating;

  function updateFloating() {
    var rect3 = getPosition(me.container);
    var offset = me.options.toolbarTopOffset || 0;
    if (rect3.top < 0 && rect3.bottom - toolbarBox.offsetHeight > offset) {
      setFloating();
    } else {
      unsetFloating();
    }
  }
  var defer_updateFloating = utils.defer(
    function() {
      updateFloating();
    },
    browser.ie ? 200 : 100,
    true
  );

  me.addListener("destroy", function() {
    domUtils.un(window, ["scroll", "resize"], updateFloating);
    me.removeListener("keydown", defer_updateFloating);
  });

  me.addListener("ready", function() {
    if (checkHasUI(me)) {
      //加载了ui组件，但在new时，没有加载ui，导致编辑器实例上没有ui类，所以这里做判断
      if (!me.ui) {
        return;
      }
      getPosition = uiUtils.getClientRect;
      toolbarBox = me.ui.getDom("toolbarbox");
      orgTop = getPosition(toolbarBox).top;
      bakCssText = toolbarBox.style.cssText;
      placeHolder.style.height = toolbarBox.offsetHeight + "px";
      if (LteIE6) {
        fixIE6FixedPos();
      }
      domUtils.on(window, ["scroll", "resize"], updateFloating);
      me.addListener("keydown", defer_updateFloating);

      me.addListener("beforefullscreenchange", function(t, fullscreen) {
        // if (fullscreen) {
          unsetFloating();
        // }
      });
      me.addListener("fullscreenchanged", function(t, fullscreen) {
        // if (!fullscreen) {
          updateFloating();
        // }
      });
      me.addListener("sourcemodechanged", function(t, enabled) {
        setTimeout(function() {
          updateFloating();
        }, 0);
      });
      me.addListener("clearDoc", function() {
        setTimeout(function() {
          updateFloating();
        }, 0);
      });
    }
  });
};


// plugins/video.js
/**
 * video插件， 为UEditor提供视频插入支持
 * @file
 * @since 1.2.6.1
 */

UE.plugins["video"] = function() {
  var me = this;

  /**
     * 创建插入视频字符窜
     * @param url 视频地址
     * @param width 视频宽度
     * @param height 视频高度
     * @param align 视频对齐
     * @param toEmbed 是否以flash代替显示
     * @param addParagraph  是否需要添加P 标签
     */
  function creatInsertStr(url, width, height, id, align, classname, type) {
    var str;
    switch (type) {
      case 'iframe':
        str = '<iframe class="' + classname + '" ' +
          ' src="' + utils.html(url) + '" width="' + width + '" height="' + height + '"' +
          ' frameborder=0 allowfullscreen>';
        break;
      case "image":
        str =
          "<img " +
          (id ? 'id="' + id + '"' : "") +
          ' width="' +
          width +
          '" height="' +
          height +
          '" _url="' +
          url +
          '" class="' +
          '"' +
          ' src="' +
          me.options.UEDITOR_HOME_URL +
          'themes/default/images/spacer.gif" style="background:url(' +
          me.options.UEDITOR_HOME_URL +
          "themes/default/images/videologo.gif) no-repeat center center; border:1px solid gray;" +
          (align ? "float:" + align + ";" : "") +
          '" />';
        break;
      case "embed":
        str =
          '<embed type="application/x-shockwave-flash" class="' +
          classname +
          '" pluginspage="http://www.macromedia.com/go/getflashplayer"' +
          ' src="' +
          utils.html(url) +
          '" width="' +
          width +
          '" height="' +
          height +
          '"' +
          (align ? ' style="float:' + align + '"' : "") +
          ' wmode="transparent" play="true" loop="false" menu="false" allowscriptaccess="never" allowfullscreen="true" >';
        break;
      case "video":
        var ext = url.substr(url.lastIndexOf(".") + 1);
        if (ext == "ogv") ext = "ogg";
        str =
          "<video" +
          (id ? ' id="' + id + '"' : "") +
          ' class="' +
          classname +
          '" ' +
          (align ? ' style="float:' + align + '"' : "") +
          ' controls preload="none" width="' +
          width +
          '" height="' +
          height +
          '" src="' +
          url +
          '" data-setup="{}">' +
          '<source src="' +
          url +
          '" type="video/' +
          ext +
          '" /></video>';
        break;
    }
    return str;
  }

  function switchImgAndVideo(root, img2video) {
    utils.each(
      root.getNodesByTagName(img2video ? "img" : "embed video"),
      function(node) {
        var className = node.getAttr("class");
        if (className && className.indexOf("edui-faked-video") != -1) {
          var html = creatInsertStr(
            img2video ? node.getAttr("_url") : node.getAttr("src"),
            node.getAttr("width"),
            node.getAttr("height"),
            null,
            node.getStyle("float") || "",
            className,
            img2video ? "embed" : "image"
          );
          node.parentNode.replaceChild(UE.uNode.createElement(html), node);
        }
        if (className && className.indexOf("edui-upload-video") != -1) {
          var html = creatInsertStr(
            img2video ? node.getAttr("_url") : node.getAttr("src"),
            node.getAttr("width"),
            node.getAttr("height"),
            null,
            node.getStyle("float") || "",
            className,
            img2video ? "video" : "image"
          );
          node.parentNode.replaceChild(UE.uNode.createElement(html), node);
        }
      }
    );
  }

  me.addOutputRule(function(root) {
    switchImgAndVideo(root, true);
  });
  me.addInputRule(function(root) {
    switchImgAndVideo(root);
  });

  /**
     * 插入视频
     * @command insertvideo
     * @method execCommand
     * @param { String } cmd 命令字符串
     * @param { Object } videoAttr 键值对对象， 描述一个视频的所有属性
     * @example
     * ```javascript
     *
     * var videoAttr = {
     *      //视频地址
     *      url: 'http://www.youku.com/xxx',
     *      //视频宽高值， 单位px
     *      width: 200,
     *      height: 100
     * };
     *
     * //editor 是编辑器实例
     * //向编辑器插入单个视频
     * editor.execCommand( 'insertvideo', videoAttr );
     * ```
     */

  /**
     * 插入视频
     * @command insertvideo
     * @method execCommand
     * @param { String } cmd 命令字符串
     * @param { Array } videoArr 需要插入的视频的数组， 其中的每一个元素都是一个键值对对象， 描述了一个视频的所有属性
     * @example
     * ```javascript
     *
     * var videoAttr1 = {
     *      //视频地址
     *      url: 'http://www.youku.com/xxx',
     *      //视频宽高值， 单位px
     *      width: 200,
     *      height: 100
     * },
     * videoAttr2 = {
     *      //视频地址
     *      url: 'http://www.youku.com/xxx',
     *      //视频宽高值， 单位px
     *      width: 200,
     *      height: 100
     * }
     *
     * //editor 是编辑器实例
     * //该方法将会向编辑器内插入两个视频
     * editor.execCommand( 'insertvideo', [ videoAttr1, videoAttr2 ] );
     * ```
     */

  /**
     * 查询当前光标所在处是否是一个视频
     * @command insertvideo
     * @method queryCommandState
     * @param { String } cmd 需要查询的命令字符串
     * @return { int } 如果当前光标所在处的元素是一个视频对象， 则返回1，否则返回0
     * @example
     * ```javascript
     *
     * //editor 是编辑器实例
     * editor.queryCommandState( 'insertvideo' );
     * ```
     */
  me.commands["insertvideo"] = {
    execCommand: function(cmd, videoObjs, type) {
      videoObjs = utils.isArray(videoObjs) ? videoObjs : [videoObjs];

      if (me.fireEvent("beforeinsertvideo", videoObjs) === true) {
        return;
      }

      var html = [],
        id = "tmpVideo",
        cl;
      for (var i = 0, vi, len = videoObjs.length; i < len; i++) {
        vi = videoObjs[i];
        var videoType = 'iframe';
        if(vi.url.match(/.mp4$/)){
          videoType = 'video';
        }
        cl = videoType == "iframe"
          ? "edui-video-iframe"
          : "edui-video-video";
        html.push(
          creatInsertStr(
            vi.url,
            vi.width || 420,
            vi.height || 280,
            id + i,
            null,
            cl,
            videoType
          )
        );
      }
      me.execCommand("inserthtml", html.join(""), true);
      var rng = this.selection.getRange();
      // for (var i = 0, len = videoObjs.length; i < len; i++) {
      //   var img = this.document.getElementById("tmpVideo" + i);
      //   domUtils.removeAttributes(img, "id");
      //   rng.selectNode(img).select();
      //   me.execCommand("imagefloat", videoObjs[i].align);
      // }

      me.fireEvent("afterinsertvideo", videoObjs);
    },
    queryCommandState: function() {
      var img = me.selection.getRange().getClosedNode(),
        flag =
          img &&
          (img.className == "edui-video-iframe" ||
            img.className.indexOf("edui-video-iframe") != -1 ||
            img.className == "edui-video-video" ||
      img.className.indexOf("edui-video-video") != -1);
      return flag ? 1 : 0;
    }
  };
};


// plugins/table.core.js
/**
 * Created with JetBrains WebStorm.
 * User: taoqili
 * Date: 13-1-18
 * Time: 上午11:09
 * To change this template use File | Settings | File Templates.
 */
/**
 * UE表格操作类
 * @param table
 * @constructor
 */
(function() {
  var UETable = (UE.UETable = function(table) {
    this.table = table;
    this.indexTable = [];
    this.selectedTds = [];
    this.cellsRange = {};
    this.update(table);
  });

  //===以下为静态工具方法===
  UETable.removeSelectedClass = function(cells) {
    utils.each(cells, function(cell) {
      domUtils.removeClasses(cell, "selectTdClass");
    });
  };
  UETable.addSelectedClass = function(cells) {
    utils.each(cells, function(cell) {
      domUtils.addClass(cell, "selectTdClass");
    });
  };
  UETable.isEmptyBlock = function(node) {
    var reg = new RegExp(domUtils.fillChar, "g");
    if (
      node[browser.ie ? "innerText" : "textContent"]
        .replace(/^\s*$/, "")
        .replace(reg, "").length > 0
    ) {
      return 0;
    }
    for (var i in dtd.$isNotEmpty)
      if (dtd.$isNotEmpty.hasOwnProperty(i)) {
        if (node.getElementsByTagName(i).length) {
          return 0;
        }
      }
    return 1;
  };
  UETable.getWidth = function(cell) {
    if (!cell) return 0;
    return parseInt(domUtils.getComputedStyle(cell, "width"), 10);
  };

  /**
     * 获取单元格或者单元格组的“对齐”状态。 如果当前的检测对象是一个单元格组， 只有在满足所有单元格的 水平和竖直 对齐属性都相同的
     * 条件时才会返回其状态值，否则将返回null； 如果当前只检测了一个单元格， 则直接返回当前单元格的对齐状态；
     * @param table cell or table cells , 支持单个单元格dom对象 或者 单元格dom对象数组
     * @return { align: 'left' || 'right' || 'center', valign: 'top' || 'middle' || 'bottom' } 或者 null
     */
  UETable.getTableCellAlignState = function(cells) {
    !utils.isArray(cells) && (cells = [cells]);

    var result = {},
      status = ["align", "valign"],
      tempStatus = null,
      isSame = true; //状态是否相同

    utils.each(cells, function(cellNode) {
      utils.each(status, function(currentState) {
        tempStatus = cellNode.getAttribute(currentState);

        if (!result[currentState] && tempStatus) {
          result[currentState] = tempStatus;
        } else if (
          !result[currentState] ||
          tempStatus !== result[currentState]
        ) {
          isSame = false;
          return false;
        }
      });

      return isSame;
    });

    return isSame ? result : null;
  };

  /**
     * 根据当前选区获取相关的table信息
     * @return {Object}
     */
  UETable.getTableItemsByRange = function(editor) {
    var start = editor.selection.getStart();

    //ff下会选中bookmark
    if (
      start &&
      start.id &&
      start.id.indexOf("_baidu_bookmark_start_") === 0 &&
      start.nextSibling
    ) {
      start = start.nextSibling;
    }

    //在table或者td边缘有可能存在选中tr的情况
    var cell = start && domUtils.findParentByTagName(start, ["td", "th"], true),
      tr = cell && cell.parentNode,
      table = tr && domUtils.findParentByTagName(tr, ["table"]),
      caption = table && table.getElementsByTagName("caption")[0];

    return {
      cell: cell,
      tr: tr,
      table: table,
      caption: caption
    };
  };
  UETable.getUETableBySelected = function(editor) {
    var table = UETable.getTableItemsByRange(editor).table;
    if (table && table.ueTable && table.ueTable.selectedTds.length) {
      return table.ueTable;
    }
    return null;
  };

  UETable.getDefaultValue = function(editor, table) {
    var borderMap = {
      thin: "0px",
      medium: "1px",
      thick: "2px"
    },
      tableBorder,
      tdPadding,
      tdBorder,
      tmpValue;
    if (!table) {
      table = editor.document.createElement("table");
      table.insertRow(0).insertCell(0).innerHTML = "xxx";
      editor.body.appendChild(table);
      var td = table.getElementsByTagName("td")[0];
      tmpValue = domUtils.getComputedStyle(table, "border-left-width");
      tableBorder = parseInt(borderMap[tmpValue] || tmpValue, 10);
      tmpValue = domUtils.getComputedStyle(td, "padding-left");
      tdPadding = parseInt(borderMap[tmpValue] || tmpValue, 10);
      tmpValue = domUtils.getComputedStyle(td, "border-left-width");
      tdBorder = parseInt(borderMap[tmpValue] || tmpValue, 10);
      domUtils.remove(table);
      return {
        tableBorder: tableBorder,
        tdPadding: tdPadding,
        tdBorder: tdBorder
      };
    } else {
      td = table.getElementsByTagName("td")[0];
      tmpValue = domUtils.getComputedStyle(table, "border-left-width");
      tableBorder = parseInt(borderMap[tmpValue] || tmpValue, 10);
      tmpValue = domUtils.getComputedStyle(td, "padding-left");
      tdPadding = parseInt(borderMap[tmpValue] || tmpValue, 10);
      tmpValue = domUtils.getComputedStyle(td, "border-left-width");
      tdBorder = parseInt(borderMap[tmpValue] || tmpValue, 10);
      return {
        tableBorder: tableBorder,
        tdPadding: tdPadding,
        tdBorder: tdBorder
      };
    }
  };
  /**
     * 根据当前点击的td或者table获取索引对象
     * @param tdOrTable
     */
  UETable.getUETable = function(tdOrTable) {
    var tag = tdOrTable.tagName.toLowerCase();
    tdOrTable = tag == "td" || tag == "th" || tag == "caption"
      ? domUtils.findParentByTagName(tdOrTable, "table", true)
      : tdOrTable;
    if (!tdOrTable.ueTable) {
      tdOrTable.ueTable = new UETable(tdOrTable);
    }
    return tdOrTable.ueTable;
  };

  UETable.cloneCell = function(cell, ignoreMerge, keepPro) {
    if (!cell || utils.isString(cell)) {
      return this.table.ownerDocument.createElement(cell || "td");
    }
    var flag = domUtils.hasClass(cell, "selectTdClass");
    flag && domUtils.removeClasses(cell, "selectTdClass");
    var tmpCell = cell.cloneNode(true);
    if (ignoreMerge) {
      tmpCell.rowSpan = tmpCell.colSpan = 1;
    }
    //去掉宽高
    !keepPro && domUtils.removeAttributes(tmpCell, "width height");
    !keepPro && domUtils.removeAttributes(tmpCell, "style");

    tmpCell.style.borderLeftStyle = "";
    tmpCell.style.borderTopStyle = "";
    tmpCell.style.borderLeftColor = cell.style.borderRightColor;
    tmpCell.style.borderLeftWidth = cell.style.borderRightWidth;
    tmpCell.style.borderTopColor = cell.style.borderBottomColor;
    tmpCell.style.borderTopWidth = cell.style.borderBottomWidth;
    flag && domUtils.addClass(cell, "selectTdClass");
    return tmpCell;
  };

  UETable.prototype = {
    getMaxRows: function() {
      var rows = this.table.rows,
        maxLen = 1;
      for (var i = 0, row; (row = rows[i]); i++) {
        var currentMax = 1;
        for (var j = 0, cj; (cj = row.cells[j++]); ) {
          currentMax = Math.max(cj.rowSpan || 1, currentMax);
        }
        maxLen = Math.max(currentMax + i, maxLen);
      }
      return maxLen;
    },
    /**
         * 获取当前表格的最大列数
         */
    getMaxCols: function() {
      var rows = this.table.rows,
        maxLen = 0,
        cellRows = {};
      for (var i = 0, row; (row = rows[i]); i++) {
        var cellsNum = 0;
        for (var j = 0, cj; (cj = row.cells[j++]); ) {
          cellsNum += cj.colSpan || 1;
          if (cj.rowSpan && cj.rowSpan > 1) {
            for (var k = 1; k < cj.rowSpan; k++) {
              if (!cellRows["row_" + (i + k)]) {
                cellRows["row_" + (i + k)] = cj.colSpan || 1;
              } else {
                cellRows["row_" + (i + k)]++;
              }
            }
          }
        }
        cellsNum += cellRows["row_" + i] || 0;
        maxLen = Math.max(cellsNum, maxLen);
      }
      return maxLen;
    },
    getCellColIndex: function(cell) {},
    /**
         * 获取当前cell旁边的单元格，
         * @param cell
         * @param right
         */
    getHSideCell: function(cell, right) {
      try {
        var cellInfo = this.getCellInfo(cell),
          previewRowIndex,
          previewColIndex;
        var len = this.selectedTds.length,
          range = this.cellsRange;
        //首行或者首列没有前置单元格
        if (
          (!right && (!len ? !cellInfo.colIndex : !range.beginColIndex)) ||
          (right &&
            (!len
              ? cellInfo.colIndex == this.colsNum - 1
              : range.endColIndex == this.colsNum - 1))
        )
          return null;

        previewRowIndex = !len ? cellInfo.rowIndex : range.beginRowIndex;
        previewColIndex = !right
          ? !len
            ? cellInfo.colIndex < 1 ? 0 : cellInfo.colIndex - 1
            : range.beginColIndex - 1
          : !len ? cellInfo.colIndex + 1 : range.endColIndex + 1;
        return this.getCell(
          this.indexTable[previewRowIndex][previewColIndex].rowIndex,
          this.indexTable[previewRowIndex][previewColIndex].cellIndex
        );
      } catch (e) {
        showError(e);
      }
    },
    getTabNextCell: function(cell, preRowIndex) {
      var cellInfo = this.getCellInfo(cell),
        rowIndex = preRowIndex || cellInfo.rowIndex,
        colIndex = cellInfo.colIndex + 1 + (cellInfo.colSpan - 1),
        nextCell;
      try {
        nextCell = this.getCell(
          this.indexTable[rowIndex][colIndex].rowIndex,
          this.indexTable[rowIndex][colIndex].cellIndex
        );
      } catch (e) {
        try {
          rowIndex = rowIndex * 1 + 1;
          colIndex = 0;
          nextCell = this.getCell(
            this.indexTable[rowIndex][colIndex].rowIndex,
            this.indexTable[rowIndex][colIndex].cellIndex
          );
        } catch (e) {}
      }
      return nextCell;
    },
    /**
         * 获取视觉上的后置单元格
         * @param cell
         * @param bottom
         */
    getVSideCell: function(cell, bottom, ignoreRange) {
      try {
        var cellInfo = this.getCellInfo(cell),
          nextRowIndex,
          nextColIndex;
        var len = this.selectedTds.length && !ignoreRange,
          range = this.cellsRange;
        //末行或者末列没有后置单元格
        if (
          (!bottom && cellInfo.rowIndex == 0) ||
          (bottom &&
            (!len
              ? cellInfo.rowIndex + cellInfo.rowSpan > this.rowsNum - 1
              : range.endRowIndex == this.rowsNum - 1))
        )
          return null;

        nextRowIndex = !bottom
          ? !len ? cellInfo.rowIndex - 1 : range.beginRowIndex - 1
          : !len ? cellInfo.rowIndex + cellInfo.rowSpan : range.endRowIndex + 1;
        nextColIndex = !len ? cellInfo.colIndex : range.beginColIndex;
        return this.getCell(
          this.indexTable[nextRowIndex][nextColIndex].rowIndex,
          this.indexTable[nextRowIndex][nextColIndex].cellIndex
        );
      } catch (e) {
        showError(e);
      }
    },
    /**
         * 获取相同结束位置的单元格，xOrY指代了是获取x轴相同还是y轴相同
         */
    getSameEndPosCells: function(cell, xOrY) {
      try {
        var flag = xOrY.toLowerCase() === "x",
          end =
            domUtils.getXY(cell)[flag ? "x" : "y"] +
            cell["offset" + (flag ? "Width" : "Height")],
          rows = this.table.rows,
          cells = null,
          returns = [];
        for (var i = 0; i < this.rowsNum; i++) {
          cells = rows[i].cells;
          for (var j = 0, tmpCell; (tmpCell = cells[j++]); ) {
            var tmpEnd =
              domUtils.getXY(tmpCell)[flag ? "x" : "y"] +
              tmpCell["offset" + (flag ? "Width" : "Height")];
            //对应行的td已经被上面行rowSpan了
            if (tmpEnd > end && flag) break;
            if (cell == tmpCell || end == tmpEnd) {
              //只获取单一的单元格
              //todo 仅获取单一单元格在特定情况下会造成returns为空，从而影响后续的拖拽实现，修正这个。需考虑性能
              if (tmpCell[flag ? "colSpan" : "rowSpan"] == 1) {
                returns.push(tmpCell);
              }
              if (flag) break;
            }
          }
        }
        return returns;
      } catch (e) {
        showError(e);
      }
    },
    setCellContent: function(cell, content) {
      cell.innerHTML = content || (browser.ie ? domUtils.fillChar : "<br />");
    },
    cloneCell: UETable.cloneCell,
    /**
         * 获取跟当前单元格的右边竖线为左边的所有未合并单元格
         */
    getSameStartPosXCells: function(cell) {
      try {
        var start = domUtils.getXY(cell).x + cell.offsetWidth,
          rows = this.table.rows,
          cells,
          returns = [];
        for (var i = 0; i < this.rowsNum; i++) {
          cells = rows[i].cells;
          for (var j = 0, tmpCell; (tmpCell = cells[j++]); ) {
            var tmpStart = domUtils.getXY(tmpCell).x;
            if (tmpStart > start) break;
            if (tmpStart == start && tmpCell.colSpan == 1) {
              returns.push(tmpCell);
              break;
            }
          }
        }
        return returns;
      } catch (e) {
        showError(e);
      }
    },
    /**
         * 更新table对应的索引表
         */
    update: function(table) {
      this.table = table || this.table;
      this.selectedTds = [];
      this.cellsRange = {};
      this.indexTable = [];
      var rows = this.table.rows,
        rowsNum = this.getMaxRows(),
        dNum = rowsNum - rows.length,
        colsNum = this.getMaxCols();
      while (dNum--) {
        this.table.insertRow(rows.length);
      }
      this.rowsNum = rowsNum;
      this.colsNum = colsNum;
      for (var i = 0, len = rows.length; i < len; i++) {
        this.indexTable[i] = new Array(colsNum);
      }
      //填充索引表
      for (var rowIndex = 0, row; (row = rows[rowIndex]); rowIndex++) {
        for (
          var cellIndex = 0, cell, cells = row.cells;
          (cell = cells[cellIndex]);
          cellIndex++
        ) {
          //修正整行被rowSpan时导致的行数计算错误
          if (cell.rowSpan > rowsNum) {
            cell.rowSpan = rowsNum;
          }
          var colIndex = cellIndex,
            rowSpan = cell.rowSpan || 1,
            colSpan = cell.colSpan || 1;
          //当已经被上一行rowSpan或者被前一列colSpan了，则跳到下一个单元格进行
          while (this.indexTable[rowIndex][colIndex]) colIndex++;
          for (var j = 0; j < rowSpan; j++) {
            for (var k = 0; k < colSpan; k++) {
              this.indexTable[rowIndex + j][colIndex + k] = {
                rowIndex: rowIndex,
                cellIndex: cellIndex,
                colIndex: colIndex,
                rowSpan: rowSpan,
                colSpan: colSpan
              };
            }
          }
        }
      }
      //修复残缺td
      for (j = 0; j < rowsNum; j++) {
        for (k = 0; k < colsNum; k++) {
          if (this.indexTable[j][k] === undefined) {
            row = rows[j];
            cell = row.cells[row.cells.length - 1];
            cell = cell
              ? cell.cloneNode(true)
              : this.table.ownerDocument.createElement("td");
            this.setCellContent(cell);
            if (cell.colSpan !== 1) cell.colSpan = 1;
            if (cell.rowSpan !== 1) cell.rowSpan = 1;
            row.appendChild(cell);
            this.indexTable[j][k] = {
              rowIndex: j,
              cellIndex: cell.cellIndex,
              colIndex: k,
              rowSpan: 1,
              colSpan: 1
            };
          }
        }
      }
      //当框选后删除行或者列后撤销，需要重建选区。
      var tds = domUtils.getElementsByTagName(this.table, "td"),
        selectTds = [];
      utils.each(tds, function(td) {
        if (domUtils.hasClass(td, "selectTdClass")) {
          selectTds.push(td);
        }
      });
      if (selectTds.length) {
        var start = selectTds[0],
          end = selectTds[selectTds.length - 1],
          startInfo = this.getCellInfo(start),
          endInfo = this.getCellInfo(end);
        this.selectedTds = selectTds;
        this.cellsRange = {
          beginRowIndex: startInfo.rowIndex,
          beginColIndex: startInfo.colIndex,
          endRowIndex: endInfo.rowIndex + endInfo.rowSpan - 1,
          endColIndex: endInfo.colIndex + endInfo.colSpan - 1
        };
      }
      //给第一行设置firstRow的样式名称,在排序图标的样式上使用到
      if (!domUtils.hasClass(this.table.rows[0], "firstRow")) {
        domUtils.addClass(this.table.rows[0], "firstRow");
        for (var i = 1; i < this.table.rows.length; i++) {
          domUtils.removeClasses(this.table.rows[i], "firstRow");
        }
      }
    },
    /**
         * 获取单元格的索引信息
         */
    getCellInfo: function(cell) {
      if (!cell) return;
      var cellIndex = cell.cellIndex,
        rowIndex = cell.parentNode.rowIndex,
        rowInfo = this.indexTable[rowIndex],
        numCols = this.colsNum;
      for (var colIndex = cellIndex; colIndex < numCols; colIndex++) {
        var cellInfo = rowInfo[colIndex];
        if (
          cellInfo.rowIndex === rowIndex &&
          cellInfo.cellIndex === cellIndex
        ) {
          return cellInfo;
        }
      }
    },
    /**
         * 根据行列号获取单元格
         */
    getCell: function(rowIndex, cellIndex) {
      return (
        (rowIndex < this.rowsNum &&
          this.table.rows[rowIndex].cells[cellIndex]) ||
        null
      );
    },
    /**
         * 删除单元格
         */
    deleteCell: function(cell, rowIndex) {
      rowIndex = typeof rowIndex == "number"
        ? rowIndex
        : cell.parentNode.rowIndex;
      var row = this.table.rows[rowIndex];
      row.deleteCell(cell.cellIndex);
    },
    /**
         * 根据始末两个单元格获取被框选的所有单元格范围
         */
    getCellsRange: function(cellA, cellB) {
      function checkRange(
        beginRowIndex,
        beginColIndex,
        endRowIndex,
        endColIndex
      ) {
        var tmpBeginRowIndex = beginRowIndex,
          tmpBeginColIndex = beginColIndex,
          tmpEndRowIndex = endRowIndex,
          tmpEndColIndex = endColIndex,
          cellInfo,
          colIndex,
          rowIndex;
        // 通过indexTable检查是否存在超出TableRange上边界的情况
        if (beginRowIndex > 0) {
          for (colIndex = beginColIndex; colIndex < endColIndex; colIndex++) {
            cellInfo = me.indexTable[beginRowIndex][colIndex];
            rowIndex = cellInfo.rowIndex;
            if (rowIndex < beginRowIndex) {
              tmpBeginRowIndex = Math.min(rowIndex, tmpBeginRowIndex);
            }
          }
        }
        // 通过indexTable检查是否存在超出TableRange右边界的情况
        if (endColIndex < me.colsNum) {
          for (rowIndex = beginRowIndex; rowIndex < endRowIndex; rowIndex++) {
            cellInfo = me.indexTable[rowIndex][endColIndex];
            colIndex = cellInfo.colIndex + cellInfo.colSpan - 1;
            if (colIndex > endColIndex) {
              tmpEndColIndex = Math.max(colIndex, tmpEndColIndex);
            }
          }
        }
        // 检查是否有超出TableRange下边界的情况
        if (endRowIndex < me.rowsNum) {
          for (colIndex = beginColIndex; colIndex < endColIndex; colIndex++) {
            cellInfo = me.indexTable[endRowIndex][colIndex];
            rowIndex = cellInfo.rowIndex + cellInfo.rowSpan - 1;
            if (rowIndex > endRowIndex) {
              tmpEndRowIndex = Math.max(rowIndex, tmpEndRowIndex);
            }
          }
        }
        // 检查是否有超出TableRange左边界的情况
        if (beginColIndex > 0) {
          for (rowIndex = beginRowIndex; rowIndex < endRowIndex; rowIndex++) {
            cellInfo = me.indexTable[rowIndex][beginColIndex];
            colIndex = cellInfo.colIndex;
            if (colIndex < beginColIndex) {
              tmpBeginColIndex = Math.min(cellInfo.colIndex, tmpBeginColIndex);
            }
          }
        }
        //递归调用直至所有完成所有框选单元格的扩展
        if (
          tmpBeginRowIndex != beginRowIndex ||
          tmpBeginColIndex != beginColIndex ||
          tmpEndRowIndex != endRowIndex ||
          tmpEndColIndex != endColIndex
        ) {
          return checkRange(
            tmpBeginRowIndex,
            tmpBeginColIndex,
            tmpEndRowIndex,
            tmpEndColIndex
          );
        } else {
          // 不需要扩展TableRange的情况
          return {
            beginRowIndex: beginRowIndex,
            beginColIndex: beginColIndex,
            endRowIndex: endRowIndex,
            endColIndex: endColIndex
          };
        }
      }

      try {
        var me = this,
          cellAInfo = me.getCellInfo(cellA);
        if (cellA === cellB) {
          return {
            beginRowIndex: cellAInfo.rowIndex,
            beginColIndex: cellAInfo.colIndex,
            endRowIndex: cellAInfo.rowIndex + cellAInfo.rowSpan - 1,
            endColIndex: cellAInfo.colIndex + cellAInfo.colSpan - 1
          };
        }
        var cellBInfo = me.getCellInfo(cellB);
        // 计算TableRange的四个边
        var beginRowIndex = Math.min(cellAInfo.rowIndex, cellBInfo.rowIndex),
          beginColIndex = Math.min(cellAInfo.colIndex, cellBInfo.colIndex),
          endRowIndex = Math.max(
            cellAInfo.rowIndex + cellAInfo.rowSpan - 1,
            cellBInfo.rowIndex + cellBInfo.rowSpan - 1
          ),
          endColIndex = Math.max(
            cellAInfo.colIndex + cellAInfo.colSpan - 1,
            cellBInfo.colIndex + cellBInfo.colSpan - 1
          );

        return checkRange(
          beginRowIndex,
          beginColIndex,
          endRowIndex,
          endColIndex
        );
      } catch (e) {
        //throw e;
      }
    },
    /**
         * 依据cellsRange获取对应的单元格集合
         */
    getCells: function(range) {
      //每次获取cells之前必须先清除上次的选择，否则会对后续获取操作造成影响
      this.clearSelected();
      var beginRowIndex = range.beginRowIndex,
        beginColIndex = range.beginColIndex,
        endRowIndex = range.endRowIndex,
        endColIndex = range.endColIndex,
        cellInfo,
        rowIndex,
        colIndex,
        tdHash = {},
        returnTds = [];
      for (var i = beginRowIndex; i <= endRowIndex; i++) {
        for (var j = beginColIndex; j <= endColIndex; j++) {
          cellInfo = this.indexTable[i][j];
          rowIndex = cellInfo.rowIndex;
          colIndex = cellInfo.colIndex;
          // 如果Cells里已经包含了此Cell则跳过
          var key = rowIndex + "|" + colIndex;
          if (tdHash[key]) continue;
          tdHash[key] = 1;
          if (
            rowIndex < i ||
            colIndex < j ||
            rowIndex + cellInfo.rowSpan - 1 > endRowIndex ||
            colIndex + cellInfo.colSpan - 1 > endColIndex
          ) {
            return null;
          }
          returnTds.push(this.getCell(rowIndex, cellInfo.cellIndex));
        }
      }
      return returnTds;
    },
    /**
         * 清理已经选中的单元格
         */
    clearSelected: function() {
      UETable.removeSelectedClass(this.selectedTds);
      this.selectedTds = [];
      this.cellsRange = {};
    },
    /**
         * 根据range设置已经选中的单元格
         */
    setSelected: function(range) {
      var cells = this.getCells(range);
      UETable.addSelectedClass(cells);
      this.selectedTds = cells;
      this.cellsRange = range;
    },
    isFullRow: function() {
      var range = this.cellsRange;
      return range.endColIndex - range.beginColIndex + 1 == this.colsNum;
    },
    isFullCol: function() {
      var range = this.cellsRange,
        table = this.table,
        ths = table.getElementsByTagName("th"),
        rows = range.endRowIndex - range.beginRowIndex + 1;
      return !ths.length
        ? rows == this.rowsNum
        : rows == this.rowsNum || rows == this.rowsNum - 1;
    },
    /**
         * 获取视觉上的前置单元格，默认是左边，top传入时
         * @param cell
         * @param top
         */
    getNextCell: function(cell, bottom, ignoreRange) {
      try {
        var cellInfo = this.getCellInfo(cell),
          nextRowIndex,
          nextColIndex;
        var len = this.selectedTds.length && !ignoreRange,
          range = this.cellsRange;
        //末行或者末列没有后置单元格
        if (
          (!bottom && cellInfo.rowIndex == 0) ||
          (bottom &&
            (!len
              ? cellInfo.rowIndex + cellInfo.rowSpan > this.rowsNum - 1
              : range.endRowIndex == this.rowsNum - 1))
        )
          return null;

        nextRowIndex = !bottom
          ? !len ? cellInfo.rowIndex - 1 : range.beginRowIndex - 1
          : !len ? cellInfo.rowIndex + cellInfo.rowSpan : range.endRowIndex + 1;
        nextColIndex = !len ? cellInfo.colIndex : range.beginColIndex;
        return this.getCell(
          this.indexTable[nextRowIndex][nextColIndex].rowIndex,
          this.indexTable[nextRowIndex][nextColIndex].cellIndex
        );
      } catch (e) {
        showError(e);
      }
    },
    getPreviewCell: function(cell, top) {
      try {
        var cellInfo = this.getCellInfo(cell),
          previewRowIndex,
          previewColIndex;
        var len = this.selectedTds.length,
          range = this.cellsRange;
        //首行或者首列没有前置单元格
        if (
          (!top && (!len ? !cellInfo.colIndex : !range.beginColIndex)) ||
          (top &&
            (!len
              ? cellInfo.rowIndex > this.colsNum - 1
              : range.endColIndex == this.colsNum - 1))
        )
          return null;

        previewRowIndex = !top
          ? !len ? cellInfo.rowIndex : range.beginRowIndex
          : !len
            ? cellInfo.rowIndex < 1 ? 0 : cellInfo.rowIndex - 1
            : range.beginRowIndex;
        previewColIndex = !top
          ? !len
            ? cellInfo.colIndex < 1 ? 0 : cellInfo.colIndex - 1
            : range.beginColIndex - 1
          : !len ? cellInfo.colIndex : range.endColIndex + 1;
        return this.getCell(
          this.indexTable[previewRowIndex][previewColIndex].rowIndex,
          this.indexTable[previewRowIndex][previewColIndex].cellIndex
        );
      } catch (e) {
        showError(e);
      }
    },
    /**
         * 移动单元格中的内容
         */
    moveContent: function(cellTo, cellFrom) {
      if (UETable.isEmptyBlock(cellFrom)) return;
      if (UETable.isEmptyBlock(cellTo)) {
        cellTo.innerHTML = cellFrom.innerHTML;
        return;
      }
      var child = cellTo.lastChild;
      if (child.nodeType == 3 || !dtd.$block[child.tagName]) {
        cellTo.appendChild(cellTo.ownerDocument.createElement("br"));
      }
      while ((child = cellFrom.firstChild)) {
        cellTo.appendChild(child);
      }
    },
    /**
         * 向右合并单元格
         */
    mergeRight: function(cell) {
      var cellInfo = this.getCellInfo(cell),
        rightColIndex = cellInfo.colIndex + cellInfo.colSpan,
        rightCellInfo = this.indexTable[cellInfo.rowIndex][rightColIndex],
        rightCell = this.getCell(
          rightCellInfo.rowIndex,
          rightCellInfo.cellIndex
        );
      //合并
      cell.colSpan = cellInfo.colSpan + rightCellInfo.colSpan;
      //被合并的单元格不应存在宽度属性
      cell.removeAttribute("width");
      //移动内容
      this.moveContent(cell, rightCell);
      //删掉被合并的Cell
      this.deleteCell(rightCell, rightCellInfo.rowIndex);
      this.update();
    },
    /**
         * 向下合并单元格
         */
    mergeDown: function(cell) {
      var cellInfo = this.getCellInfo(cell),
        downRowIndex = cellInfo.rowIndex + cellInfo.rowSpan,
        downCellInfo = this.indexTable[downRowIndex][cellInfo.colIndex],
        downCell = this.getCell(downCellInfo.rowIndex, downCellInfo.cellIndex);
      cell.rowSpan = cellInfo.rowSpan + downCellInfo.rowSpan;
      cell.removeAttribute("height");
      this.moveContent(cell, downCell);
      this.deleteCell(downCell, downCellInfo.rowIndex);
      this.update();
    },
    /**
         * 合并整个range中的内容
         */
    mergeRange: function() {
      //由于合并操作可以在任意时刻进行，所以无法通过鼠标位置等信息实时生成range，只能通过缓存实例中的cellsRange对象来访问
      var range = this.cellsRange,
        leftTopCell = this.getCell(
          range.beginRowIndex,
          this.indexTable[range.beginRowIndex][range.beginColIndex].cellIndex
        );

      // 这段关于行表头或者列表头的特殊处理会导致表头合并范围错误
      // 为什么有这段代码的原因未明，暂且注释掉，希望原作者看到后出面说明下
      // if (
      //   leftTopCell.tagName == "TH" &&
      //   range.endRowIndex !== range.beginRowIndex
      // ) {
      //   var index = this.indexTable,
      //     info = this.getCellInfo(leftTopCell);
      //   leftTopCell = this.getCell(1, index[1][info.colIndex].cellIndex);
      //   range = this.getCellsRange(
      //     leftTopCell,
      //     this.getCell(
      //       index[this.rowsNum - 1][info.colIndex].rowIndex,
      //       index[this.rowsNum - 1][info.colIndex].cellIndex
      //     )
      //   );
      // }

      // 删除剩余的Cells
      var cells = this.getCells(range);
      for (var i = 0, ci; (ci = cells[i++]); ) {
        if (ci !== leftTopCell) {
          this.moveContent(leftTopCell, ci);
          this.deleteCell(ci);
        }
      }
      // 修改左上角Cell的rowSpan和colSpan，并调整宽度属性设置
      leftTopCell.rowSpan = range.endRowIndex - range.beginRowIndex + 1;
      leftTopCell.rowSpan > 1 && leftTopCell.removeAttribute("height");
      leftTopCell.colSpan = range.endColIndex - range.beginColIndex + 1;
      leftTopCell.colSpan > 1 && leftTopCell.removeAttribute("width");
      if (leftTopCell.rowSpan == this.rowsNum && leftTopCell.colSpan != 1) {
        leftTopCell.colSpan = 1;
      }

      if (leftTopCell.colSpan == this.colsNum && leftTopCell.rowSpan != 1) {
        var rowIndex = leftTopCell.parentNode.rowIndex;
        //解决IE下的表格操作问题
        if (this.table.deleteRow) {
          for (
            var i = rowIndex + 1,
              curIndex = rowIndex + 1,
              len = leftTopCell.rowSpan;
            i < len;
            i++
          ) {
            this.table.deleteRow(curIndex);
          }
        } else {
          for (var i = 0, len = leftTopCell.rowSpan - 1; i < len; i++) {
            var row = this.table.rows[rowIndex + 1];
            row.parentNode.removeChild(row);
          }
        }
        leftTopCell.rowSpan = 1;
      }
      this.update();
    },
    /**
         * 插入一行单元格
         */
    insertRow: function(rowIndex, sourceCell) {
      var numCols = this.colsNum,
        table = this.table,
        row = table.insertRow(rowIndex),
        cell,
        thead = null,
        isInsertTitle =
          typeof sourceCell == "string" && sourceCell.toUpperCase() == "TH";

      function replaceTdToTh(colIndex, cell, tableRow) {
        if (colIndex == 0) {
          var tr = tableRow.nextSibling || tableRow.previousSibling,
            th = tr.cells[colIndex];
          if (th.tagName == "TH") {
            th = cell.ownerDocument.createElement("th");
            th.appendChild(cell.firstChild);
            tableRow.insertBefore(th, cell);
            domUtils.remove(cell);
          }
        } else {
          if (cell.tagName == "TH") {
            var td = cell.ownerDocument.createElement("td");
            td.appendChild(cell.firstChild);
            tableRow.insertBefore(td, cell);
            domUtils.remove(cell);
          }
        }
      }

      //首行直接插入,无需考虑部分单元格被rowspan的情况
      if (rowIndex == 0 || rowIndex == this.rowsNum) {
        for (var colIndex = 0; colIndex < numCols; colIndex++) {
          cell = this.cloneCell(sourceCell, true);
          this.setCellContent(cell);
          cell.getAttribute("vAlign") &&
            cell.setAttribute("vAlign", cell.getAttribute("vAlign"));
          row.appendChild(cell);
          if (!isInsertTitle) replaceTdToTh(colIndex, cell, row);
        }

        if (isInsertTitle) {
          thead = table.createTHead();
          thead.insertBefore(row, thead.firstChild);
        }
      } else {
        var infoRow = this.indexTable[rowIndex],
          cellIndex = 0;
        for (colIndex = 0; colIndex < numCols; colIndex++) {
          var cellInfo = infoRow[colIndex];
          //如果存在某个单元格的rowspan穿过待插入行的位置，则修改该单元格的rowspan即可，无需插入单元格
          if (cellInfo.rowIndex < rowIndex) {
            cell = this.getCell(cellInfo.rowIndex, cellInfo.cellIndex);
            cell.rowSpan = cellInfo.rowSpan + 1;
          } else {
            cell = this.cloneCell(sourceCell, true);
            this.setCellContent(cell);
            row.appendChild(cell);
          }
          if (!isInsertTitle) replaceTdToTh(colIndex, cell, row);
        }
      }
      //框选时插入不触发contentchange，需要手动更新索引。
      this.update();
      return row;
    },
    /**
         * 删除一行单元格
         * @param rowIndex
         */
    deleteRow: function(rowIndex) {
      var row = this.table.rows[rowIndex],
        infoRow = this.indexTable[rowIndex],
        colsNum = this.colsNum,
        count = 0; //处理计数
      for (var colIndex = 0; colIndex < colsNum; ) {
        var cellInfo = infoRow[colIndex],
          cell = this.getCell(cellInfo.rowIndex, cellInfo.cellIndex);
        if (cell.rowSpan > 1) {
          if (cellInfo.rowIndex == rowIndex) {
            var clone = cell.cloneNode(true);
            clone.rowSpan = cell.rowSpan - 1;
            clone.innerHTML = "";
            cell.rowSpan = 1;
            var nextRowIndex = rowIndex + 1,
              nextRow = this.table.rows[nextRowIndex],
              insertCellIndex,
              preMerged =
                this.getPreviewMergedCellsNum(nextRowIndex, colIndex) - count;
            if (preMerged < colIndex) {
              insertCellIndex = colIndex - preMerged - 1;
              //nextRow.insertCell(insertCellIndex);
              domUtils.insertAfter(nextRow.cells[insertCellIndex], clone);
            } else {
              if (nextRow.cells.length)
                nextRow.insertBefore(clone, nextRow.cells[0]);
            }
            count += 1;
            //cell.parentNode.removeChild(cell);
          }
        }
        colIndex += cell.colSpan || 1;
      }
      var deleteTds = [],
        cacheMap = {};
      for (colIndex = 0; colIndex < colsNum; colIndex++) {
        var tmpRowIndex = infoRow[colIndex].rowIndex,
          tmpCellIndex = infoRow[colIndex].cellIndex,
          key = tmpRowIndex + "_" + tmpCellIndex;
        if (cacheMap[key]) continue;
        cacheMap[key] = 1;
        cell = this.getCell(tmpRowIndex, tmpCellIndex);
        deleteTds.push(cell);
      }
      var mergeTds = [];
      utils.each(deleteTds, function(td) {
        if (td.rowSpan == 1) {
          td.parentNode.removeChild(td);
        } else {
          mergeTds.push(td);
        }
      });
      utils.each(mergeTds, function(td) {
        td.rowSpan--;
      });
      row.parentNode.removeChild(row);
      //浏览器方法本身存在bug,采用自定义方法删除
      //this.table.deleteRow(rowIndex);
      this.update();
    },
    insertCol: function(colIndex, sourceCell, defaultValue) {
      var rowsNum = this.rowsNum,
        rowIndex = 0,
        tableRow,
        cell,
        backWidth = parseInt(
          (this.table.offsetWidth -
            (this.colsNum + 1) * 20 -
            (this.colsNum + 1)) /
            (this.colsNum + 1),
          10
        ),
        isInsertTitleCol =
          typeof sourceCell == "string" && sourceCell.toUpperCase() == "TH";

      function replaceTdToTh(rowIndex, cell, tableRow) {
        if (rowIndex == 0) {
          var th = cell.nextSibling || cell.previousSibling;
          if (th.tagName == "TH") {
            th = cell.ownerDocument.createElement("th");
            th.appendChild(cell.firstChild);
            tableRow.insertBefore(th, cell);
            domUtils.remove(cell);
          }
        } else {
          if (cell.tagName == "TH") {
            var td = cell.ownerDocument.createElement("td");
            td.appendChild(cell.firstChild);
            tableRow.insertBefore(td, cell);
            domUtils.remove(cell);
          }
        }
      }

      var preCell;
      if (colIndex == 0 || colIndex == this.colsNum) {
        for (; rowIndex < rowsNum; rowIndex++) {
          tableRow = this.table.rows[rowIndex];
          preCell =
            tableRow.cells[colIndex == 0 ? colIndex : tableRow.cells.length];
          cell = this.cloneCell(sourceCell, true); //tableRow.insertCell(colIndex == 0 ? colIndex : tableRow.cells.length);
          this.setCellContent(cell);
          cell.setAttribute("vAlign", cell.getAttribute("vAlign"));
          preCell && cell.setAttribute("width", preCell.getAttribute("width"));
          if (!colIndex) {
            tableRow.insertBefore(cell, tableRow.cells[0]);
          } else {
            domUtils.insertAfter(
              tableRow.cells[tableRow.cells.length - 1],
              cell
            );
          }
          if (!isInsertTitleCol) replaceTdToTh(rowIndex, cell, tableRow);
        }
      } else {
        for (; rowIndex < rowsNum; rowIndex++) {
          var cellInfo = this.indexTable[rowIndex][colIndex];
          if (cellInfo.colIndex < colIndex) {
            cell = this.getCell(cellInfo.rowIndex, cellInfo.cellIndex);
            cell.colSpan = cellInfo.colSpan + 1;
          } else {
            tableRow = this.table.rows[rowIndex];
            preCell = tableRow.cells[cellInfo.cellIndex];

            cell = this.cloneCell(sourceCell, true); //tableRow.insertCell(cellInfo.cellIndex);
            this.setCellContent(cell);
            cell.setAttribute("vAlign", cell.getAttribute("vAlign"));
            preCell &&
              cell.setAttribute("width", preCell.getAttribute("width"));
            //防止IE下报错
            preCell
              ? tableRow.insertBefore(cell, preCell)
              : tableRow.appendChild(cell);
          }
          if (!isInsertTitleCol) replaceTdToTh(rowIndex, cell, tableRow);
        }
      }
      //框选时插入不触发contentchange，需要手动更新索引
      this.update();
      this.updateWidth(
        backWidth,
        defaultValue || { tdPadding: 10, tdBorder: 1 }
      );
    },
    updateWidth: function(width, defaultValue) {
      var table = this.table,
        tmpWidth =
          UETable.getWidth(table) -
          defaultValue.tdPadding * 2 -
          defaultValue.tdBorder +
          width;
      if (tmpWidth < table.ownerDocument.body.offsetWidth) {
        table.setAttribute("width", tmpWidth);
        return;
      }
      var tds = domUtils.getElementsByTagName(this.table, "td th");
      utils.each(tds, function(td) {
        td.setAttribute("width", width);
      });
    },
    deleteCol: function(colIndex) {
      var indexTable = this.indexTable,
        tableRows = this.table.rows,
        backTableWidth = this.table.getAttribute("width"),
        backTdWidth = 0,
        rowsNum = this.rowsNum,
        cacheMap = {};
      for (var rowIndex = 0; rowIndex < rowsNum; ) {
        var infoRow = indexTable[rowIndex],
          cellInfo = infoRow[colIndex],
          key = cellInfo.rowIndex + "_" + cellInfo.colIndex;
        // 跳过已经处理过的Cell
        if (cacheMap[key]) continue;
        cacheMap[key] = 1;
        var cell = this.getCell(cellInfo.rowIndex, cellInfo.cellIndex);
        if (!backTdWidth)
          backTdWidth =
            cell && parseInt(cell.offsetWidth / cell.colSpan, 10).toFixed(0);
        // 如果Cell的colSpan大于1, 就修改colSpan, 否则就删掉这个Cell
        if (cell.colSpan > 1) {
          cell.colSpan--;
        } else {
          tableRows[rowIndex].deleteCell(cellInfo.cellIndex);
        }
        rowIndex += cellInfo.rowSpan || 1;
      }
      this.table.setAttribute("width", backTableWidth - backTdWidth);
      this.update();
    },
    splitToCells: function(cell) {
      var me = this,
        cells = this.splitToRows(cell);
      utils.each(cells, function(cell) {
        me.splitToCols(cell);
      });
    },
    splitToRows: function(cell) {
      var cellInfo = this.getCellInfo(cell),
        rowIndex = cellInfo.rowIndex,
        colIndex = cellInfo.colIndex,
        results = [];
      // 修改Cell的rowSpan
      cell.rowSpan = 1;
      results.push(cell);
      // 补齐单元格
      for (
        var i = rowIndex, endRow = rowIndex + cellInfo.rowSpan;
        i < endRow;
        i++
      ) {
        if (i == rowIndex) continue;
        var tableRow = this.table.rows[i],
          tmpCell = tableRow.insertCell(
            colIndex - this.getPreviewMergedCellsNum(i, colIndex)
          );
        tmpCell.colSpan = cellInfo.colSpan;
        this.setCellContent(tmpCell);
        tmpCell.setAttribute("vAlign", cell.getAttribute("vAlign"));
        tmpCell.setAttribute("align", cell.getAttribute("align"));
        if (cell.style.cssText) {
          tmpCell.style.cssText = cell.style.cssText;
        }
        results.push(tmpCell);
      }
      this.update();
      return results;
    },
    getPreviewMergedCellsNum: function(rowIndex, colIndex) {
      var indexRow = this.indexTable[rowIndex],
        num = 0;
      for (var i = 0; i < colIndex; ) {
        var colSpan = indexRow[i].colSpan,
          tmpRowIndex = indexRow[i].rowIndex;
        num += colSpan - (tmpRowIndex == rowIndex ? 1 : 0);
        i += colSpan;
      }
      return num;
    },
    splitToCols: function(cell) {
      var backWidth = (cell.offsetWidth / cell.colSpan - 22).toFixed(0),
        cellInfo = this.getCellInfo(cell),
        rowIndex = cellInfo.rowIndex,
        colIndex = cellInfo.colIndex,
        results = [];
      // 修改Cell的rowSpan
      cell.colSpan = 1;
      cell.setAttribute("width", backWidth);
      results.push(cell);
      // 补齐单元格
      for (
        var j = colIndex, endCol = colIndex + cellInfo.colSpan;
        j < endCol;
        j++
      ) {
        if (j == colIndex) continue;
        var tableRow = this.table.rows[rowIndex],
          tmpCell = tableRow.insertCell(
            this.indexTable[rowIndex][j].cellIndex + 1
          );
        tmpCell.rowSpan = cellInfo.rowSpan;
        this.setCellContent(tmpCell);
        tmpCell.setAttribute("vAlign", cell.getAttribute("vAlign"));
        tmpCell.setAttribute("align", cell.getAttribute("align"));
        tmpCell.setAttribute("width", backWidth);
        if (cell.style.cssText) {
          tmpCell.style.cssText = cell.style.cssText;
        }
        //处理th的情况
        if (cell.tagName == "TH") {
          var th = cell.ownerDocument.createElement("th");
          th.appendChild(tmpCell.firstChild);
          th.setAttribute("vAlign", cell.getAttribute("vAlign"));
          th.rowSpan = tmpCell.rowSpan;
          tableRow.insertBefore(th, tmpCell);
          domUtils.remove(tmpCell);
        }
        results.push(tmpCell);
      }
      this.update();
      return results;
    },
    isLastCell: function(cell, rowsNum, colsNum) {
      rowsNum = rowsNum || this.rowsNum;
      colsNum = colsNum || this.colsNum;
      var cellInfo = this.getCellInfo(cell);
      return (
        cellInfo.rowIndex + cellInfo.rowSpan == rowsNum &&
        cellInfo.colIndex + cellInfo.colSpan == colsNum
      );
    },
    getLastCell: function(cells) {
      cells = cells || this.table.getElementsByTagName("td");
      var firstInfo = this.getCellInfo(cells[0]);
      var me = this,
        last = cells[0],
        tr = last.parentNode,
        cellsNum = 0,
        cols = 0,
        rows;
      utils.each(cells, function(cell) {
        if (cell.parentNode == tr) cols += cell.colSpan || 1;
        cellsNum += cell.rowSpan * cell.colSpan || 1;
      });
      rows = cellsNum / cols;
      utils.each(cells, function(cell) {
        if (me.isLastCell(cell, rows, cols)) {
          last = cell;
          return false;
        }
      });
      return last;
    },
    selectRow: function(rowIndex) {
      var indexRow = this.indexTable[rowIndex],
        start = this.getCell(indexRow[0].rowIndex, indexRow[0].cellIndex),
        end = this.getCell(
          indexRow[this.colsNum - 1].rowIndex,
          indexRow[this.colsNum - 1].cellIndex
        ),
        range = this.getCellsRange(start, end);
      this.setSelected(range);
    },
    selectTable: function() {
      var tds = this.table.getElementsByTagName("td"),
        range = this.getCellsRange(tds[0], tds[tds.length - 1]);
      this.setSelected(range);
    },
    setBackground: function(cells, value) {
      if (typeof value === "string") {
        utils.each(cells, function(cell) {
          cell.style.backgroundColor = value;
        });
      } else if (typeof value === "object") {
        value = utils.extend(
          {
            repeat: true,
            colorList: ["#ddd", "#fff"]
          },
          value
        );
        var rowIndex = this.getCellInfo(cells[0]).rowIndex,
          count = 0,
          colors = value.colorList,
          getColor = function(list, index, repeat) {
            return list[index]
              ? list[index]
              : repeat ? list[index % list.length] : "";
          };
        for (var i = 0, cell; (cell = cells[i++]); ) {
          var cellInfo = this.getCellInfo(cell);
          cell.style.backgroundColor = getColor(
            colors,
            rowIndex + count == cellInfo.rowIndex ? count : ++count,
            value.repeat
          );
        }
      }
    },
    removeBackground: function(cells) {
      utils.each(cells, function(cell) {
        cell.style.backgroundColor = "";
      });
    }
  };
  function showError(e) {}
})();


// plugins/table.cmds.js
/**
 * Created with JetBrains PhpStorm.
 * User: taoqili
 * Date: 13-2-20
 * Time: 下午6:25
 * To change this template use File | Settings | File Templates.
 */
(function() {
  var UT = UE.UETable,
    getTableItemsByRange = function(editor) {
      return UT.getTableItemsByRange(editor);
    },
    getUETableBySelected = function(editor) {
      return UT.getUETableBySelected(editor);
    },
    getDefaultValue = function(editor, table) {
      return UT.getDefaultValue(editor, table);
    },
    getUETable = function(tdOrTable) {
      return UT.getUETable(tdOrTable);
    };

  UE.commands["inserttable"] = {
    queryCommandState: function() {
      return getTableItemsByRange(this).table ? -1 : 0;
    },
    execCommand: function(cmd, opt) {
      function createTable(opt, tdWidth) {
        var html = [],
          rowsNum = opt.numRows,
          colsNum = opt.numCols;
        for (var r = 0; r < rowsNum; r++) {
          html.push("<tr" + (r == 0 ? ' class="firstRow"' : "") + ">");
          for (var c = 0; c < colsNum; c++) {
            html.push(
              '<td width="' +
                tdWidth +
                '"  vAlign="' +
                opt.tdvalign +
                '" >' +
                (browser.ie && browser.version < 11
                  ? domUtils.fillChar
                  : "<br/>") +
                "</td>"
            );
          }
          html.push("</tr>");
        }
        //禁止指定table-width
        return "<table><tbody>" + html.join("") + "</tbody></table>";
      }

      if (!opt) {
        opt = utils.extend(
          {},
          {
            numCols: this.options.defaultCols,
            numRows: this.options.defaultRows,
            tdvalign: this.options.tdvalign
          }
        );
      }
      var me = this;
      var range = this.selection.getRange(),
        start = range.startContainer,
        firstParentBlock =
          domUtils.findParent(
            start,
            function(node) {
              return domUtils.isBlockElm(node);
            },
            true
          ) || me.body;

      var defaultValue = getDefaultValue(me),
        tableWidth = firstParentBlock.offsetWidth,
        tdWidth = Math.floor(
          tableWidth / opt.numCols -
            defaultValue.tdPadding * 2 -
            defaultValue.tdBorder
        );

      //todo其他属性
      !opt.tdvalign && (opt.tdvalign = me.options.tdvalign);
      me.execCommand("inserthtml", createTable(opt, tdWidth));
    }
  };

  UE.commands["insertparagraphbeforetable"] = {
    queryCommandState: function() {
      return getTableItemsByRange(this).cell ? 0 : -1;
    },
    execCommand: function() {
      var table = getTableItemsByRange(this).table;
      if (table) {
        var p = this.document.createElement("p");
        p.innerHTML = browser.ie ? "&nbsp;" : "<br />";
        table.parentNode.insertBefore(p, table);
        this.selection.getRange().setStart(p, 0).setCursor();
      }
    }
  };

  UE.commands["deletetable"] = {
    queryCommandState: function() {
      var rng = this.selection.getRange();
      return domUtils.findParentByTagName(rng.startContainer, "table", true)
        ? 0
        : -1;
    },
    execCommand: function(cmd, table) {
      var rng = this.selection.getRange();
      table =
        table ||
        domUtils.findParentByTagName(rng.startContainer, "table", true);
      if (table) {
        var next = table.nextSibling;
        if (!next) {
          next = domUtils.createElement(this.document, "p", {
            innerHTML: browser.ie ? domUtils.fillChar : "<br/>"
          });
          table.parentNode.insertBefore(next, table);
        }
        domUtils.remove(table);
        rng = this.selection.getRange();
        if (next.nodeType == 3) {
          rng.setStartBefore(next);
        } else {
          rng.setStart(next, 0);
        }
        rng.setCursor(false, true);
        this.fireEvent("tablehasdeleted");
      }
    }
  };
  UE.commands["cellalign"] = {
    queryCommandState: function() {
      return getSelectedArr(this).length ? 0 : -1;
    },
    execCommand: function(cmd, align) {
      var selectedTds = getSelectedArr(this);
      if (selectedTds.length) {
        for (var i = 0, ci; (ci = selectedTds[i++]); ) {
          ci.setAttribute("align", align);
        }
      }
    }
  };
  UE.commands["cellvalign"] = {
    queryCommandState: function() {
      return getSelectedArr(this).length ? 0 : -1;
    },
    execCommand: function(cmd, valign) {
      var selectedTds = getSelectedArr(this);
      if (selectedTds.length) {
        for (var i = 0, ci; (ci = selectedTds[i++]); ) {
          ci.setAttribute("vAlign", valign);
        }
      }
    }
  };
  UE.commands["insertcaption"] = {
    queryCommandState: function() {
      var table = getTableItemsByRange(this).table;
      if (table) {
        return table.getElementsByTagName("caption").length == 0 ? 1 : -1;
      }
      return -1;
    },
    execCommand: function() {
      var table = getTableItemsByRange(this).table;
      if (table) {
        var caption = this.document.createElement("caption");
        caption.innerHTML = browser.ie ? domUtils.fillChar : "<br/>";
        table.insertBefore(caption, table.firstChild);
        var range = this.selection.getRange();
        range.setStart(caption, 0).setCursor();
      }
    }
  };
  UE.commands["deletecaption"] = {
    queryCommandState: function() {
      var rng = this.selection.getRange(),
        table = domUtils.findParentByTagName(rng.startContainer, "table");
      if (table) {
        return table.getElementsByTagName("caption").length == 0 ? -1 : 1;
      }
      return -1;
    },
    execCommand: function() {
      var rng = this.selection.getRange(),
        table = domUtils.findParentByTagName(rng.startContainer, "table");
      if (table) {
        domUtils.remove(table.getElementsByTagName("caption")[0]);
        var range = this.selection.getRange();
        range.setStart(table.rows[0].cells[0], 0).setCursor();
      }
    }
  };
  UE.commands["inserttitle"] = {
    queryCommandState: function() {
      var table = getTableItemsByRange(this).table;
      if (table) {
        var firstRow = table.rows[0];
        return firstRow.cells[
          firstRow.cells.length - 1
        ].tagName.toLowerCase() != "th"
          ? 0
          : -1;
      }
      return -1;
    },
    execCommand: function() {
      var table = getTableItemsByRange(this).table;
      if (table) {
        getUETable(table).insertRow(0, "th");
      }
      var th = table.getElementsByTagName("th")[0];
      this.selection.getRange().setStart(th, 0).setCursor(false, true);
    }
  };
  UE.commands["deletetitle"] = {
    queryCommandState: function() {
      var table = getTableItemsByRange(this).table;
      if (table) {
        var firstRow = table.rows[0];
        return firstRow.cells[
          firstRow.cells.length - 1
        ].tagName.toLowerCase() == "th"
          ? 0
          : -1;
      }
      return -1;
    },
    execCommand: function() {
      var table = getTableItemsByRange(this).table;
      if (table) {
        domUtils.remove(table.rows[0]);
      }
      var td = table.getElementsByTagName("td")[0];
      this.selection.getRange().setStart(td, 0).setCursor(false, true);
    }
  };
  UE.commands["inserttitlecol"] = {
    queryCommandState: function() {
      var table = getTableItemsByRange(this).table;
      if (table) {
        var lastRow = table.rows[table.rows.length - 1];
        return lastRow.getElementsByTagName("th").length ? -1 : 0;
      }
      return -1;
    },
    execCommand: function(cmd) {
      var table = getTableItemsByRange(this).table;
      if (table) {
        getUETable(table).insertCol(0, "th");
      }
      resetTdWidth(table, this);
      var th = table.getElementsByTagName("th")[0];
      this.selection.getRange().setStart(th, 0).setCursor(false, true);
    }
  };
  UE.commands["deletetitlecol"] = {
    queryCommandState: function() {
      var table = getTableItemsByRange(this).table;
      if (table) {
        var lastRow = table.rows[table.rows.length - 1];
        return lastRow.getElementsByTagName("th").length ? 0 : -1;
      }
      return -1;
    },
    execCommand: function() {
      var table = getTableItemsByRange(this).table;
      if (table) {
        for (var i = 0; i < table.rows.length; i++) {
          domUtils.remove(table.rows[i].children[0]);
        }
      }
      resetTdWidth(table, this);
      var td = table.getElementsByTagName("td")[0];
      this.selection.getRange().setStart(td, 0).setCursor(false, true);
    }
  };

  UE.commands["mergeright"] = {
    queryCommandState: function(cmd) {
      var tableItems = getTableItemsByRange(this),
        table = tableItems.table,
        cell = tableItems.cell;

      if (!table || !cell) return -1;
      var ut = getUETable(table);
      if (ut.selectedTds.length) return -1;

      var cellInfo = ut.getCellInfo(cell),
        rightColIndex = cellInfo.colIndex + cellInfo.colSpan;
      if (rightColIndex >= ut.colsNum) return -1; // 如果处于最右边则不能向右合并

      var rightCellInfo = ut.indexTable[cellInfo.rowIndex][rightColIndex],
        rightCell =
          table.rows[rightCellInfo.rowIndex].cells[rightCellInfo.cellIndex];
      if (!rightCell || cell.tagName != rightCell.tagName) return -1; // TH和TD不能相互合并

      // 当且仅当两个Cell的开始列号和结束列号一致时能进行合并
      return rightCellInfo.rowIndex == cellInfo.rowIndex &&
        rightCellInfo.rowSpan == cellInfo.rowSpan
        ? 0
        : -1;
    },
    execCommand: function(cmd) {
      var rng = this.selection.getRange(),
        bk = rng.createBookmark(true);
      var cell = getTableItemsByRange(this).cell,
        ut = getUETable(cell);
      ut.mergeRight(cell);
      rng.moveToBookmark(bk).select();
    }
  };
  UE.commands["mergedown"] = {
    queryCommandState: function(cmd) {
      var tableItems = getTableItemsByRange(this),
        table = tableItems.table,
        cell = tableItems.cell;

      if (!table || !cell) return -1;
      var ut = getUETable(table);
      if (ut.selectedTds.length) return -1;

      var cellInfo = ut.getCellInfo(cell),
        downRowIndex = cellInfo.rowIndex + cellInfo.rowSpan;
      if (downRowIndex >= ut.rowsNum) return -1; // 如果处于最下边则不能向下合并

      var downCellInfo = ut.indexTable[downRowIndex][cellInfo.colIndex],
        downCell =
          table.rows[downCellInfo.rowIndex].cells[downCellInfo.cellIndex];
      if (!downCell || cell.tagName != downCell.tagName) return -1; // TH和TD不能相互合并

      // 当且仅当两个Cell的开始列号和结束列号一致时能进行合并
      return downCellInfo.colIndex == cellInfo.colIndex &&
        downCellInfo.colSpan == cellInfo.colSpan
        ? 0
        : -1;
    },
    execCommand: function() {
      var rng = this.selection.getRange(),
        bk = rng.createBookmark(true);
      var cell = getTableItemsByRange(this).cell,
        ut = getUETable(cell);
      ut.mergeDown(cell);
      rng.moveToBookmark(bk).select();
    }
  };
  UE.commands["mergecells"] = {
    queryCommandState: function() {
      return getUETableBySelected(this) ? 0 : -1;
    },
    execCommand: function() {
      var ut = getUETableBySelected(this);
      if (ut && ut.selectedTds.length) {
        var cell = ut.selectedTds[0];
        ut.mergeRange();
        var rng = this.selection.getRange();
        if (domUtils.isEmptyBlock(cell)) {
          rng.setStart(cell, 0).collapse(true);
        } else {
          rng.selectNodeContents(cell);
        }
        rng.select();
      }
    }
  };
  UE.commands["insertrow"] = {
    queryCommandState: function() {
      var tableItems = getTableItemsByRange(this),
        cell = tableItems.cell;
      return cell &&
        (cell.tagName == "TD" ||
          (cell.tagName == "TH" &&
            tableItems.tr !== tableItems.table.rows[0])) &&
        getUETable(tableItems.table).rowsNum < this.options.maxRowNum
        ? 0
        : -1;
    },
    execCommand: function() {
      var rng = this.selection.getRange(),
        bk = rng.createBookmark(true);
      var tableItems = getTableItemsByRange(this),
        cell = tableItems.cell,
        table = tableItems.table,
        ut = getUETable(table),
        cellInfo = ut.getCellInfo(cell);
      //ut.insertRow(!ut.selectedTds.length ? cellInfo.rowIndex:ut.cellsRange.beginRowIndex,'');
      if (!ut.selectedTds.length) {
        ut.insertRow(cellInfo.rowIndex, cell);
      } else {
        var range = ut.cellsRange;
        for (
          var i = 0, len = range.endRowIndex - range.beginRowIndex + 1;
          i < len;
          i++
        ) {
          ut.insertRow(range.beginRowIndex, cell);
        }
      }
      rng.moveToBookmark(bk).select();
      if (table.getAttribute("interlaced") === "enabled")
        this.fireEvent("interlacetable", table);
    }
  };
  //后插入行
  UE.commands["insertrownext"] = {
    queryCommandState: function() {
      var tableItems = getTableItemsByRange(this),
        cell = tableItems.cell;
      return cell &&
        cell.tagName == "TD" &&
        getUETable(tableItems.table).rowsNum < this.options.maxRowNum
        ? 0
        : -1;
    },
    execCommand: function() {
      var rng = this.selection.getRange(),
        bk = rng.createBookmark(true);
      var tableItems = getTableItemsByRange(this),
        cell = tableItems.cell,
        table = tableItems.table,
        ut = getUETable(table),
        cellInfo = ut.getCellInfo(cell);
      //ut.insertRow(!ut.selectedTds.length? cellInfo.rowIndex + cellInfo.rowSpan : ut.cellsRange.endRowIndex + 1,'');
      if (!ut.selectedTds.length) {
        ut.insertRow(cellInfo.rowIndex + cellInfo.rowSpan, cell);
      } else {
        var range = ut.cellsRange;
        for (
          var i = 0, len = range.endRowIndex - range.beginRowIndex + 1;
          i < len;
          i++
        ) {
          ut.insertRow(range.endRowIndex + 1, cell);
        }
      }
      rng.moveToBookmark(bk).select();
      if (table.getAttribute("interlaced") === "enabled")
        this.fireEvent("interlacetable", table);
    }
  };
  UE.commands["deleterow"] = {
    queryCommandState: function() {
      var tableItems = getTableItemsByRange(this);
      return tableItems.cell ? 0 : -1;
    },
    execCommand: function() {
      var cell = getTableItemsByRange(this).cell,
        ut = getUETable(cell),
        cellsRange = ut.cellsRange,
        cellInfo = ut.getCellInfo(cell),
        preCell = ut.getVSideCell(cell),
        nextCell = ut.getVSideCell(cell, true),
        rng = this.selection.getRange();
      if (utils.isEmptyObject(cellsRange)) {
        ut.deleteRow(cellInfo.rowIndex);
      } else {
        for (
          var i = cellsRange.beginRowIndex;
          i < cellsRange.endRowIndex + 1;
          i++
        ) {
          ut.deleteRow(cellsRange.beginRowIndex);
        }
      }
      var table = ut.table;
      if (!table.getElementsByTagName("td").length) {
        var nextSibling = table.nextSibling;
        domUtils.remove(table);
        if (nextSibling) {
          rng.setStart(nextSibling, 0).setCursor(false, true);
        }
      } else {
        if (
          cellInfo.rowSpan == 1 ||
          cellInfo.rowSpan ==
            cellsRange.endRowIndex - cellsRange.beginRowIndex + 1
        ) {
          if (nextCell || preCell)
            rng.selectNodeContents(nextCell || preCell).setCursor(false, true);
        } else {
          var newCell = ut.getCell(
            cellInfo.rowIndex,
            ut.indexTable[cellInfo.rowIndex][cellInfo.colIndex].cellIndex
          );
          if (newCell) rng.selectNodeContents(newCell).setCursor(false, true);
        }
      }
      if (table.getAttribute("interlaced") === "enabled")
        this.fireEvent("interlacetable", table);
    }
  };
  UE.commands["insertcol"] = {
    queryCommandState: function(cmd) {
      var tableItems = getTableItemsByRange(this),
        cell = tableItems.cell;
      return cell &&
        (cell.tagName == "TD" ||
          (cell.tagName == "TH" && cell !== tableItems.tr.cells[0])) &&
        getUETable(tableItems.table).colsNum < this.options.maxColNum
        ? 0
        : -1;
    },
    execCommand: function(cmd) {
      var rng = this.selection.getRange(),
        bk = rng.createBookmark(true);
      if (this.queryCommandState(cmd) == -1) return;
      var cell = getTableItemsByRange(this).cell,
        ut = getUETable(cell),
        cellInfo = ut.getCellInfo(cell);

      //ut.insertCol(!ut.selectedTds.length ? cellInfo.colIndex:ut.cellsRange.beginColIndex);
      if (!ut.selectedTds.length) {
        ut.insertCol(cellInfo.colIndex, cell);
      } else {
        var range = ut.cellsRange;
        for (
          var i = 0, len = range.endColIndex - range.beginColIndex + 1;
          i < len;
          i++
        ) {
          ut.insertCol(range.beginColIndex, cell);
        }
      }
      rng.moveToBookmark(bk).select(true);
    }
  };
  UE.commands["insertcolnext"] = {
    queryCommandState: function() {
      var tableItems = getTableItemsByRange(this),
        cell = tableItems.cell;
      return cell &&
        getUETable(tableItems.table).colsNum < this.options.maxColNum
        ? 0
        : -1;
    },
    execCommand: function() {
      var rng = this.selection.getRange(),
        bk = rng.createBookmark(true);
      var cell = getTableItemsByRange(this).cell,
        ut = getUETable(cell),
        cellInfo = ut.getCellInfo(cell);
      //ut.insertCol(!ut.selectedTds.length ? cellInfo.colIndex + cellInfo.colSpan:ut.cellsRange.endColIndex +1);
      if (!ut.selectedTds.length) {
        ut.insertCol(cellInfo.colIndex + cellInfo.colSpan, cell);
      } else {
        var range = ut.cellsRange;
        for (
          var i = 0, len = range.endColIndex - range.beginColIndex + 1;
          i < len;
          i++
        ) {
          ut.insertCol(range.endColIndex + 1, cell);
        }
      }
      rng.moveToBookmark(bk).select();
    }
  };

  UE.commands["deletecol"] = {
    queryCommandState: function() {
      var tableItems = getTableItemsByRange(this);
      return tableItems.cell ? 0 : -1;
    },
    execCommand: function() {
      var cell = getTableItemsByRange(this).cell,
        ut = getUETable(cell),
        range = ut.cellsRange,
        cellInfo = ut.getCellInfo(cell),
        preCell = ut.getHSideCell(cell),
        nextCell = ut.getHSideCell(cell, true);
      if (utils.isEmptyObject(range)) {
        ut.deleteCol(cellInfo.colIndex);
      } else {
        for (var i = range.beginColIndex; i < range.endColIndex + 1; i++) {
          ut.deleteCol(range.beginColIndex);
        }
      }
      var table = ut.table,
        rng = this.selection.getRange();

      if (!table.getElementsByTagName("td").length) {
        var nextSibling = table.nextSibling;
        domUtils.remove(table);
        if (nextSibling) {
          rng.setStart(nextSibling, 0).setCursor(false, true);
        }
      } else {
        if (domUtils.inDoc(cell, this.document)) {
          rng.setStart(cell, 0).setCursor(false, true);
        } else {
          if (nextCell && domUtils.inDoc(nextCell, this.document)) {
            rng.selectNodeContents(nextCell).setCursor(false, true);
          } else {
            if (preCell && domUtils.inDoc(preCell, this.document)) {
              rng.selectNodeContents(preCell).setCursor(true, true);
            }
          }
        }
      }
    }
  };
  UE.commands["splittocells"] = {
    queryCommandState: function() {
      var tableItems = getTableItemsByRange(this),
        cell = tableItems.cell;
      if (!cell) return -1;
      var ut = getUETable(tableItems.table);
      if (ut.selectedTds.length > 0) return -1;
      return cell && (cell.colSpan > 1 || cell.rowSpan > 1) ? 0 : -1;
    },
    execCommand: function() {
      var rng = this.selection.getRange(),
        bk = rng.createBookmark(true);
      var cell = getTableItemsByRange(this).cell,
        ut = getUETable(cell);
      ut.splitToCells(cell);
      rng.moveToBookmark(bk).select();
    }
  };
  UE.commands["splittorows"] = {
    queryCommandState: function() {
      var tableItems = getTableItemsByRange(this),
        cell = tableItems.cell;
      if (!cell) return -1;
      var ut = getUETable(tableItems.table);
      if (ut.selectedTds.length > 0) return -1;
      return cell && cell.rowSpan > 1 ? 0 : -1;
    },
    execCommand: function() {
      var rng = this.selection.getRange(),
        bk = rng.createBookmark(true);
      var cell = getTableItemsByRange(this).cell,
        ut = getUETable(cell);
      ut.splitToRows(cell);
      rng.moveToBookmark(bk).select();
    }
  };
  UE.commands["splittocols"] = {
    queryCommandState: function() {
      var tableItems = getTableItemsByRange(this),
        cell = tableItems.cell;
      if (!cell) return -1;
      var ut = getUETable(tableItems.table);
      if (ut.selectedTds.length > 0) return -1;
      return cell && cell.colSpan > 1 ? 0 : -1;
    },
    execCommand: function() {
      var rng = this.selection.getRange(),
        bk = rng.createBookmark(true);
      var cell = getTableItemsByRange(this).cell,
        ut = getUETable(cell);
      ut.splitToCols(cell);
      rng.moveToBookmark(bk).select();
    }
  };

  UE.commands["adaptbytext"] = UE.commands["adaptbywindow"] = {
    queryCommandState: function() {
      return getTableItemsByRange(this).table ? 0 : -1;
    },
    execCommand: function(cmd) {
      var tableItems = getTableItemsByRange(this),
        table = tableItems.table;
      if (table) {
        if (cmd == "adaptbywindow") {
          resetTdWidth(table, this);
        } else {
          var cells = domUtils.getElementsByTagName(table, "td th");
          utils.each(cells, function(cell) {
            cell.removeAttribute("width");
          });
          table.removeAttribute("width");
        }
      }
    }
  };

  //平均分配各列
  UE.commands["averagedistributecol"] = {
    queryCommandState: function() {
      var ut = getUETableBySelected(this);
      if (!ut) return -1;
      return ut.isFullRow() || ut.isFullCol() ? 0 : -1;
    },
    execCommand: function(cmd) {
      var me = this,
        ut = getUETableBySelected(me);

      function getAverageWidth() {
        var tb = ut.table,
          averageWidth,
          sumWidth = 0,
          colsNum = 0,
          tbAttr = getDefaultValue(me, tb);

        if (ut.isFullRow()) {
          sumWidth = tb.offsetWidth;
          colsNum = ut.colsNum;
        } else {
          var begin = ut.cellsRange.beginColIndex,
            end = ut.cellsRange.endColIndex,
            node;
          for (var i = begin; i <= end; ) {
            node = ut.selectedTds[i];
            sumWidth += node.offsetWidth;
            i += node.colSpan;
            colsNum += 1;
          }
        }
        averageWidth =
          Math.ceil(sumWidth / colsNum) -
          tbAttr.tdBorder * 2 -
          tbAttr.tdPadding * 2;
        return averageWidth;
      }

      function setAverageWidth(averageWidth) {
        utils.each(domUtils.getElementsByTagName(ut.table, "th"), function(
          node
        ) {
          node.setAttribute("width", "");
        });
        var cells = ut.isFullRow()
          ? domUtils.getElementsByTagName(ut.table, "td")
          : ut.selectedTds;

        utils.each(cells, function(node) {
          if (node.colSpan == 1) {
            node.setAttribute("width", averageWidth);
          }
        });
      }

      if (ut && ut.selectedTds.length) {
        setAverageWidth(getAverageWidth());
      }
    }
  };
  //平均分配各行
  UE.commands["averagedistributerow"] = {
    queryCommandState: function() {
      var ut = getUETableBySelected(this);
      if (!ut) return -1;
      if (ut.selectedTds && /th/gi.test(ut.selectedTds[0].tagName)) return -1;
      return ut.isFullRow() || ut.isFullCol() ? 0 : -1;
    },
    execCommand: function(cmd) {
      var me = this,
        ut = getUETableBySelected(me);

      function getAverageHeight() {
        var averageHeight,
          rowNum,
          sumHeight = 0,
          tb = ut.table,
          tbAttr = getDefaultValue(me, tb),
          tdpadding = parseInt(
            domUtils.getComputedStyle(
              tb.getElementsByTagName("td")[0],
              "padding-top"
            )
          );

        if (ut.isFullCol()) {
          var captionArr = domUtils.getElementsByTagName(tb, "caption"),
            thArr = domUtils.getElementsByTagName(tb, "th"),
            captionHeight,
            thHeight;

          if (captionArr.length > 0) {
            captionHeight = captionArr[0].offsetHeight;
          }
          if (thArr.length > 0) {
            thHeight = thArr[0].offsetHeight;
          }

          sumHeight = tb.offsetHeight - (captionHeight || 0) - (thHeight || 0);
          rowNum = thArr.length == 0 ? ut.rowsNum : ut.rowsNum - 1;
        } else {
          var begin = ut.cellsRange.beginRowIndex,
            end = ut.cellsRange.endRowIndex,
            count = 0,
            trs = domUtils.getElementsByTagName(tb, "tr");
          for (var i = begin; i <= end; i++) {
            sumHeight += trs[i].offsetHeight;
            count += 1;
          }
          rowNum = count;
        }
        //ie8下是混杂模式
        if (browser.ie && browser.version < 9) {
          averageHeight = Math.ceil(sumHeight / rowNum);
        } else {
          averageHeight =
            Math.ceil(sumHeight / rowNum) - tbAttr.tdBorder * 2 - tdpadding * 2;
        }
        return averageHeight;
      }

      function setAverageHeight(averageHeight) {
        var cells = ut.isFullCol()
          ? domUtils.getElementsByTagName(ut.table, "td")
          : ut.selectedTds;
        utils.each(cells, function(node) {
          if (node.rowSpan == 1) {
            node.setAttribute("height", averageHeight);
          }
        });
      }

      if (ut && ut.selectedTds.length) {
        setAverageHeight(getAverageHeight());
      }
    }
  };

  //单元格对齐方式
  UE.commands["cellalignment"] = {
    queryCommandState: function() {
      return getTableItemsByRange(this).table ? 0 : -1;
    },
    execCommand: function(cmd, data) {
      var me = this,
        ut = getUETableBySelected(me);

      if (!ut) {
        var start = me.selection.getStart(),
          cell =
            start &&
            domUtils.findParentByTagName(start, ["td", "th", "caption"], true);
        if (!/caption/gi.test(cell.tagName)) {
          domUtils.setAttributes(cell, data);
        } else {
          cell.style.textAlign = data.align;
          cell.style.verticalAlign = data.vAlign;
        }
        me.selection.getRange().setCursor(true);
      } else {
        utils.each(ut.selectedTds, function(cell) {
          domUtils.setAttributes(cell, data);
        });
      }
    },
    /**
         * 查询当前点击的单元格的对齐状态， 如果当前已经选择了多个单元格， 则会返回所有单元格经过统一协调过后的状态
         * @see UE.UETable.getTableCellAlignState
         */
    queryCommandValue: function(cmd) {
      var activeMenuCell = getTableItemsByRange(this).cell;

      if (!activeMenuCell) {
        activeMenuCell = getSelectedArr(this)[0];
      }

      if (!activeMenuCell) {
        return null;
      } else {
        //获取同时选中的其他单元格
        var cells = UE.UETable.getUETable(activeMenuCell).selectedTds;

        !cells.length && (cells = activeMenuCell);

        return UE.UETable.getTableCellAlignState(cells);
      }
    }
  };
  //表格对齐方式
  UE.commands["tablealignment"] = {
    queryCommandState: function() {
      if (browser.ie && browser.version < 8) {
        return -1;
      }
      return getTableItemsByRange(this).table ? 0 : -1;
    },
    execCommand: function(cmd, value) {
      var me = this,
        start = me.selection.getStart(),
        table = start && domUtils.findParentByTagName(start, ["table"], true);

      if (table) {
        table.setAttribute("align", value);
      }
    }
  };

  //表格属性
  UE.commands["edittable"] = {
    queryCommandState: function() {
      return getTableItemsByRange(this).table ? 0 : -1;
    },
    execCommand: function(cmd, color) {
      var rng = this.selection.getRange(),
        table = domUtils.findParentByTagName(rng.startContainer, "table");
      if (table) {
        var arr = domUtils
          .getElementsByTagName(table, "td")
          .concat(
            domUtils.getElementsByTagName(table, "th"),
            domUtils.getElementsByTagName(table, "caption")
          );
        utils.each(arr, function(node) {
          node.style.borderColor = color;
        });
      }
    }
  };
  //单元格属性
  UE.commands["edittd"] = {
    queryCommandState: function() {
      return getTableItemsByRange(this).table ? 0 : -1;
    },
    execCommand: function(cmd, bkColor) {
      var me = this,
        ut = getUETableBySelected(me);

      if (!ut) {
        var start = me.selection.getStart(),
          cell =
            start &&
            domUtils.findParentByTagName(start, ["td", "th", "caption"], true);
        if (cell) {
          cell.style.backgroundColor = bkColor;
        }
      } else {
        utils.each(ut.selectedTds, function(cell) {
          cell.style.backgroundColor = bkColor;
        });
      }
    }
  };

  UE.commands["settablebackground"] = {
    queryCommandState: function() {
      return getSelectedArr(this).length > 1 ? 0 : -1;
    },
    execCommand: function(cmd, value) {
      var cells, ut;
      cells = getSelectedArr(this);
      ut = getUETable(cells[0]);
      ut.setBackground(cells, value);
    }
  };

  UE.commands["cleartablebackground"] = {
    queryCommandState: function() {
      var cells = getSelectedArr(this);
      if (!cells.length) return -1;
      for (var i = 0, cell; (cell = cells[i++]); ) {
        if (cell.style.backgroundColor !== "") return 0;
      }
      return -1;
    },
    execCommand: function() {
      var cells = getSelectedArr(this),
        ut = getUETable(cells[0]);
      ut.removeBackground(cells);
    }
  };

  UE.commands["interlacetable"] = UE.commands["uninterlacetable"] = {
    queryCommandState: function(cmd) {
      var table = getTableItemsByRange(this).table;
      if (!table) return -1;
      var interlaced = table.getAttribute("interlaced");
      if (cmd == "interlacetable") {
        //TODO 待定
        //是否需要待定，如果设置，则命令只能单次执行成功，但反射具备toggle效果；否则可以覆盖前次命令，但反射将不存在toggle效果
        return interlaced === "enabled" ? -1 : 0;
      } else {
        return !interlaced || interlaced === "disabled" ? -1 : 0;
      }
    },
    execCommand: function(cmd, classList) {
      var table = getTableItemsByRange(this).table;
      if (cmd == "interlacetable") {
        table.setAttribute("interlaced", "enabled");
        this.fireEvent("interlacetable", table, classList);
      } else {
        table.setAttribute("interlaced", "disabled");
        this.fireEvent("uninterlacetable", table);
      }
    }
  };
  UE.commands["setbordervisible"] = {
    queryCommandState: function(cmd) {
      var table = getTableItemsByRange(this).table;
      if (!table) return -1;
      return 0;
    },
    execCommand: function() {
      var table = getTableItemsByRange(this).table;
      utils.each(domUtils.getElementsByTagName(table, "td"), function(td) {
        td.style.borderWidth = "1px";
        td.style.borderStyle = "solid";
      });
    }
  };
  function resetTdWidth(table, editor) {
    var tds = domUtils.getElementsByTagName(table, "td th");
    utils.each(tds, function(td) {
      td.removeAttribute("width");
    });
    table.setAttribute(
      "width",
      getTableWidth(editor, true, getDefaultValue(editor, table))
    );
    var tdsWidths = [];
    setTimeout(function() {
      utils.each(tds, function(td) {
        td.colSpan == 1 && tdsWidths.push(td.offsetWidth);
      });
      utils.each(tds, function(td, i) {
        td.colSpan == 1 && td.setAttribute("width", tdsWidths[i] + "");
      });
    }, 0);
  }

  function getTableWidth(editor, needIEHack, defaultValue) {
    var body = editor.body;
    return (
      body.offsetWidth -
      (needIEHack
        ? parseInt(domUtils.getComputedStyle(body, "margin-left"), 10) * 2
        : 0) -
      defaultValue.tableBorder * 2 -
      (editor.options.offsetWidth || 0)
    );
  }

  function getSelectedArr(editor) {
    var cell = getTableItemsByRange(editor).cell;
    if (cell) {
      var ut = getUETable(cell);
      return ut.selectedTds.length ? ut.selectedTds : [cell];
    } else {
      return [];
    }
  }
})();


// plugins/table.action.js
/**
 * Created with JetBrains PhpStorm.
 * User: taoqili
 * Date: 12-10-12
 * Time: 上午10:05
 * To change this template use File | Settings | File Templates.
 */
UE.plugins["table"] = function() {
  var me = this,
    tabTimer = null,
    //拖动计时器
    tableDragTimer = null,
    //双击计时器
    tableResizeTimer = null,
    //单元格最小宽度
    cellMinWidth = 5,
    isInResizeBuffer = false,
    //单元格边框大小
    cellBorderWidth = 5,
    //鼠标偏移距离
    offsetOfTableCell = 10,
    //记录在有限时间内的点击状态， 共有3个取值， 0, 1, 2。 0代表未初始化， 1代表单击了1次，2代表2次
    singleClickState = 0,
    userActionStatus = null,
    //双击允许的时间范围
    dblclickTime = 200,
    UT = UE.UETable,
    getUETable = function(tdOrTable) {
      return UT.getUETable(tdOrTable);
    },
    getUETableBySelected = function(editor) {
      return UT.getUETableBySelected(editor);
    },
    getDefaultValue = function(editor, table) {
      return UT.getDefaultValue(editor, table);
    },
    removeSelectedClass = function(cells) {
      return UT.removeSelectedClass(cells);
    };

  function showError(e) {
    //        throw e;
  }
  me.ready(function() {
    var me = this;
    var orgGetText = me.selection.getText;
    me.selection.getText = function() {
      var table = getUETableBySelected(me);
      if (table) {
        var str = "";
        utils.each(table.selectedTds, function(td) {
          str += td[browser.ie ? "innerText" : "textContent"];
        });
        return str;
      } else {
        return orgGetText.call(me.selection);
      }
    };
  });

  //处理拖动及框选相关方法
  var startTd = null, //鼠标按下时的锚点td
    currentTd = null, //当前鼠标经过时的td
    onDrag = "", //指示当前拖动状态，其值可为"","h","v" ,分别表示未拖动状态，横向拖动状态，纵向拖动状态，用于鼠标移动过程中的判断
    onBorder = false, //检测鼠标按下时是否处在单元格边缘位置
    dragButton = null,
    dragOver = false,
    dragLine = null, //模拟的拖动线
    dragTd = null; //发生拖动的目标td

  var mousedown = false,
    //todo 判断混乱模式
    needIEHack = true;

  me.setOpt({
    maxColNum: 20,
    maxRowNum: 100,
    defaultCols: 5,
    defaultRows: 5,
    tdvalign: "top",
    cursorpath: me.options.UEDITOR_HOME_URL + "themes/default/images/cursor_",
    tableDragable: false,
    classList: [
      "ue-table-interlace-color-single",
      "ue-table-interlace-color-double"
    ]
  });
  me.getUETable = getUETable;
  var commands = {
    deletetable: 1,
    inserttable: 1,
    cellvalign: 1,
    insertcaption: 1,
    deletecaption: 1,
    inserttitle: 1,
    deletetitle: 1,
    mergeright: 1,
    mergedown: 1,
    mergecells: 1,
    insertrow: 1,
    insertrownext: 1,
    deleterow: 1,
    insertcol: 1,
    insertcolnext: 1,
    deletecol: 1,
    splittocells: 1,
    splittorows: 1,
    splittocols: 1,
    adaptbytext: 1,
    adaptbywindow: 1,
    adaptbycustomer: 1,
    insertparagraph: 1,
    insertparagraphbeforetable: 1,
    averagedistributecol: 1,
    averagedistributerow: 1
  };
  me.ready(function() {
    utils.cssRule(
      "table",
      //选中的td上的样式
      ".selectTdClass{background-color:#edf5fa !important}" +
        "table.noBorderTable td,table.noBorderTable th,table.noBorderTable caption{border:1px dashed #ddd !important}" +
        //插入的表格的默认样式
        "table{margin-bottom:10px;border-collapse:collapse;display:table;}" +
        "td,th{padding: 5px 10px;border: 1px solid #DDD;}" +
        "caption{border:1px dashed #DDD;border-bottom:0;padding:3px;text-align:center;}" +
        "th{border-top:1px solid #BBB;background-color:#F7F7F7;}" +
        "table tr.firstRow th{border-top-width:2px;}" +
        ".ue-table-interlace-color-single{ background-color: #fcfcfc; } .ue-table-interlace-color-double{ background-color: #f7faff; }" +
        "td p{margin:0;padding:0;}",
      me.document
    );

    var tableCopyList, isFullCol, isFullRow;
    //注册del/backspace事件
    me.addListener("keydown", function(cmd, evt) {
      var me = this;
      var keyCode = evt.keyCode || evt.which;

      if (keyCode == 8) {
        var ut = getUETableBySelected(me);
        if (ut && ut.selectedTds.length) {
          if (ut.isFullCol()) {
            me.execCommand("deletecol");
          } else if (ut.isFullRow()) {
            me.execCommand("deleterow");
          } else {
            me.fireEvent("delcells");
          }
          domUtils.preventDefault(evt);
        }

        var caption = domUtils.findParentByTagName(
          me.selection.getStart(),
          "caption",
          true
        ),
          range = me.selection.getRange();
        if (range.collapsed && caption && isEmptyBlock(caption)) {
          me.fireEvent("saveScene");
          var table = caption.parentNode;
          domUtils.remove(caption);
          if (table) {
            range.setStart(table.rows[0].cells[0], 0).setCursor(false, true);
          }
          me.fireEvent("saveScene");
        }
      }

      if (keyCode == 46) {
        ut = getUETableBySelected(me);
        if (ut) {
          me.fireEvent("saveScene");
          for (var i = 0, ci; (ci = ut.selectedTds[i++]); ) {
            domUtils.fillNode(me.document, ci);
          }
          me.fireEvent("saveScene");
          domUtils.preventDefault(evt);
        }
      }
      if (keyCode == 13) {
        var rng = me.selection.getRange(),
          caption = domUtils.findParentByTagName(
            rng.startContainer,
            "caption",
            true
          );
        if (caption) {
          var table = domUtils.findParentByTagName(caption, "table");
          if (!rng.collapsed) {
            rng.deleteContents();
            me.fireEvent("saveScene");
          } else {
            if (caption) {
              rng.setStart(table.rows[0].cells[0], 0).setCursor(false, true);
            }
          }
          domUtils.preventDefault(evt);
          return;
        }
        if (rng.collapsed) {
          var table = domUtils.findParentByTagName(rng.startContainer, "table");
          if (table) {
            var cell = table.rows[0].cells[0],
              start = domUtils.findParentByTagName(
                me.selection.getStart(),
                ["td", "th"],
                true
              ),
              preNode = table.previousSibling;
            if (
              cell === start &&
              (!preNode ||
                (preNode.nodeType == 1 && preNode.tagName == "TABLE")) &&
              domUtils.isStartInblock(rng)
            ) {
              var first = domUtils.findParent(
                me.selection.getStart(),
                function(n) {
                  return domUtils.isBlockElm(n);
                },
                true
              );
              if (
                first &&
                (/t(h|d)/i.test(first.tagName) || first === start.firstChild)
              ) {
                me.execCommand("insertparagraphbeforetable");
                domUtils.preventDefault(evt);
              }
            }
          }
        }
      }

      if ((evt.ctrlKey || evt.metaKey) && evt.keyCode == "67") {
        tableCopyList = null;
        var ut = getUETableBySelected(me);
        if (ut) {
          var tds = ut.selectedTds;
          isFullCol = ut.isFullCol();
          isFullRow = ut.isFullRow();
          tableCopyList = [[ut.cloneCell(tds[0], null, true)]];
          for (var i = 1, ci; (ci = tds[i]); i++) {
            if (ci.parentNode !== tds[i - 1].parentNode) {
              tableCopyList.push([ut.cloneCell(ci, null, true)]);
            } else {
              tableCopyList[tableCopyList.length - 1].push(
                ut.cloneCell(ci, null, true)
              );
            }
          }
        }
      }
    });
    me.addListener("tablehasdeleted", function() {
      toggleDraggableState(this, false, "", null);
      if (dragButton) domUtils.remove(dragButton);
    });

    me.addListener("beforepaste", function(cmd, html) {
      var me = this;
      var rng = me.selection.getRange();
      if (domUtils.findParentByTagName(rng.startContainer, "caption", true)) {
        var div = me.document.createElement("div");
        div.innerHTML = html.html;
        //trace:3729
        html.html = div[browser.ie9below ? "innerText" : "textContent"];
        return;
      }
      var table = getUETableBySelected(me);
      if (tableCopyList) {
        me.fireEvent("saveScene");
        var rng = me.selection.getRange();
        var td = domUtils.findParentByTagName(
          rng.startContainer,
          ["td", "th"],
          true
        ),
          tmpNode,
          preNode;
        if (td) {
          var ut = getUETable(td);
          if (isFullRow) {
            var rowIndex = ut.getCellInfo(td).rowIndex;
            if (td.tagName == "TH") {
              rowIndex++;
            }
            for (var i = 0, ci; (ci = tableCopyList[i++]); ) {
              var tr = ut.insertRow(rowIndex++, "td");
              for (var j = 0, cj; (cj = ci[j]); j++) {
                var cell = tr.cells[j];
                if (!cell) {
                  cell = tr.insertCell(j);
                }
                cell.innerHTML = cj.innerHTML;
                cj.getAttribute("width") &&
                  cell.setAttribute("width", cj.getAttribute("width"));
                cj.getAttribute("vAlign") &&
                  cell.setAttribute("vAlign", cj.getAttribute("vAlign"));
                cj.getAttribute("align") &&
                  cell.setAttribute("align", cj.getAttribute("align"));
                cj.style.cssText && (cell.style.cssText = cj.style.cssText);
              }
              for (var j = 0, cj; (cj = tr.cells[j]); j++) {
                if (!ci[j]) break;
                cj.innerHTML = ci[j].innerHTML;
                ci[j].getAttribute("width") &&
                  cj.setAttribute("width", ci[j].getAttribute("width"));
                ci[j].getAttribute("vAlign") &&
                  cj.setAttribute("vAlign", ci[j].getAttribute("vAlign"));
                ci[j].getAttribute("align") &&
                  cj.setAttribute("align", ci[j].getAttribute("align"));
                ci[j].style.cssText && (cj.style.cssText = ci[j].style.cssText);
              }
            }
          } else {
            if (isFullCol) {
              cellInfo = ut.getCellInfo(td);
              var maxColNum = 0;
              for (var j = 0, ci = tableCopyList[0], cj; (cj = ci[j++]); ) {
                maxColNum += cj.colSpan || 1;
              }
              me.__hasEnterExecCommand = true;
              for (i = 0; i < maxColNum; i++) {
                me.execCommand("insertcol");
              }
              me.__hasEnterExecCommand = false;
              td = ut.table.rows[0].cells[cellInfo.cellIndex];
              if (td.tagName == "TH") {
                td = ut.table.rows[1].cells[cellInfo.cellIndex];
              }
            }
            for (var i = 0, ci; (ci = tableCopyList[i++]); ) {
              tmpNode = td;
              for (var j = 0, cj; (cj = ci[j++]); ) {
                if (td) {
                  td.innerHTML = cj.innerHTML;
                  //todo 定制处理
                  cj.getAttribute("width") &&
                    td.setAttribute("width", cj.getAttribute("width"));
                  cj.getAttribute("vAlign") &&
                    td.setAttribute("vAlign", cj.getAttribute("vAlign"));
                  cj.getAttribute("align") &&
                    td.setAttribute("align", cj.getAttribute("align"));
                  cj.style.cssText && (td.style.cssText = cj.style.cssText);
                  preNode = td;
                  td = td.nextSibling;
                } else {
                  var cloneTd = cj.cloneNode(true);
                  domUtils.removeAttributes(cloneTd, [
                    "class",
                    "rowSpan",
                    "colSpan"
                  ]);

                  preNode.parentNode.appendChild(cloneTd);
                }
              }
              td = ut.getNextCell(tmpNode, true, true);
              if (!tableCopyList[i]) break;
              if (!td) {
                var cellInfo = ut.getCellInfo(tmpNode);
                ut.table.insertRow(ut.table.rows.length);
                ut.update();
                td = ut.getVSideCell(tmpNode, true);
              }
            }
          }
          ut.update();
        } else {
          table = me.document.createElement("table");
          for (var i = 0, ci; (ci = tableCopyList[i++]); ) {
            var tr = table.insertRow(table.rows.length);
            for (var j = 0, cj; (cj = ci[j++]); ) {
              cloneTd = UT.cloneCell(cj, null, true);
              domUtils.removeAttributes(cloneTd, ["class"]);
              tr.appendChild(cloneTd);
            }
            if (j == 2 && cloneTd.rowSpan > 1) {
              cloneTd.rowSpan = 1;
            }
          }

          var defaultValue = getDefaultValue(me),
            width =
              me.body.offsetWidth -
              (needIEHack
                ? parseInt(
                    domUtils.getComputedStyle(me.body, "margin-left"),
                    10
                  ) * 2
                : 0) -
              defaultValue.tableBorder * 2 -
              (me.options.offsetWidth || 0);
          me.execCommand(
            "insertHTML",
            "<table  " +
              (isFullCol && isFullRow ? 'width="' + width + '"' : "") +
              ">" +
              table.innerHTML
                .replace(/>\s*</g, "><")
                .replace(/\bth\b/gi, "td") +
              "</table>"
          );
        }
        me.fireEvent("contentchange");
        me.fireEvent("saveScene");
        html.html = "";
        return true;
      } else {
        var div = me.document.createElement("div"),
          tables;
        div.innerHTML = html.html;
        tables = div.getElementsByTagName("table");
        if (domUtils.findParentByTagName(me.selection.getStart(), "table")) {
          utils.each(tables, function(t) {
            domUtils.remove(t);
          });
          if (
            domUtils.findParentByTagName(
              me.selection.getStart(),
              "caption",
              true
            )
          ) {
            div.innerHTML = div[browser.ie ? "innerText" : "textContent"];
          }
        } else {
          utils.each(tables, function(table) {
            removeStyleSize(table, true);
            domUtils.removeAttributes(table, ["style", "border"]);
            utils.each(domUtils.getElementsByTagName(table, "td"), function(
              td
            ) {
              if (isEmptyBlock(td)) {
                domUtils.fillNode(me.document, td);
              }
              removeStyleSize(td, true);
              //                            domUtils.removeAttributes(td, ['style'])
            });
          });
        }
        html.html = div.innerHTML;
      }
    });

    me.addListener("afterpaste", function() {
      utils.each(domUtils.getElementsByTagName(me.body, "table"), function(
        table
      ) {
        if (table.offsetWidth > me.body.offsetWidth) {
          var defaultValue = getDefaultValue(me, table);
          table.style.width =
            me.body.offsetWidth -
            (needIEHack
              ? parseInt(
                  domUtils.getComputedStyle(me.body, "margin-left"),
                  10
                ) * 2
              : 0) -
            defaultValue.tableBorder * 2 -
            (me.options.offsetWidth || 0) +
            "px";
        }
      });
    });
    me.addListener("blur", function() {
      tableCopyList = null;
    });
    var timer;
    me.addListener("keydown", function() {
      clearTimeout(timer);
      timer = setTimeout(function() {
        var rng = me.selection.getRange(),
          cell = domUtils.findParentByTagName(
            rng.startContainer,
            ["th", "td"],
            true
          );
        if (cell) {
          var table = cell.parentNode.parentNode.parentNode;
          if (table.offsetWidth > table.getAttribute("width")) {
            cell.style.wordBreak = "break-all";
          }
        }
      }, 100);
    });
    me.addListener("selectionchange", function() {
      toggleDraggableState(me, false, "", null);
    });

    //内容变化时触发索引更新
    //todo 可否考虑标记检测，如果不涉及表格的变化就不进行索引重建和更新
    me.addListener("contentchange", function() {
      var me = this;
      //尽可能排除一些不需要更新的状况
      hideDragLine(me);
      if (getUETableBySelected(me)) return;
      var rng = me.selection.getRange();
      var start = rng.startContainer;
      start = domUtils.findParentByTagName(start, ["td", "th"], true);
      utils.each(domUtils.getElementsByTagName(me.document, "table"), function(
        table
      ) {
        if (me.fireEvent("excludetable", table) === true) return;
        table.ueTable = new UT(table);
        //trace:3742
        //                utils.each(domUtils.getElementsByTagName(me.document, 'td'), function (td) {
        //
        //                    if (domUtils.isEmptyBlock(td) && td !== start) {
        //                        domUtils.fillNode(me.document, td);
        //                        if (browser.ie && browser.version == 6) {
        //                            td.innerHTML = '&nbsp;'
        //                        }
        //                    }
        //                });
        //                utils.each(domUtils.getElementsByTagName(me.document, 'th'), function (th) {
        //                    if (domUtils.isEmptyBlock(th) && th !== start) {
        //                        domUtils.fillNode(me.document, th);
        //                        if (browser.ie && browser.version == 6) {
        //                            th.innerHTML = '&nbsp;'
        //                        }
        //                    }
        //                });
        table.onmouseover = function() {
          me.fireEvent("tablemouseover", table);
        };
        table.onmousemove = function() {
          me.fireEvent("tablemousemove", table);
          me.options.tableDragable && toggleDragButton(true, this, me);
          utils.defer(function() {
            me.fireEvent("contentchange", 50);
          }, true);
        };
        table.onmouseout = function() {
          me.fireEvent("tablemouseout", table);
          toggleDraggableState(me, false, "", null);
          hideDragLine(me);
        };
        table.onclick = function(evt) {
          evt = me.window.event || evt;
          var target = getParentTdOrTh(evt.target || evt.srcElement);
          if (!target) return;
          var ut = getUETable(target),
            table = ut.table,
            cellInfo = ut.getCellInfo(target),
            cellsRange,
            rng = me.selection.getRange();
          //                    if ("topLeft" == inPosition(table, mouseCoords(evt))) {
          //                        cellsRange = ut.getCellsRange(ut.table.rows[0].cells[0], ut.getLastCell());
          //                        ut.setSelected(cellsRange);
          //                        return;
          //                    }
          //                    if ("bottomRight" == inPosition(table, mouseCoords(evt))) {
          //
          //                        return;
          //                    }
          if (inTableSide(table, target, evt, true)) {
            var endTdCol = ut.getCell(
              ut.indexTable[ut.rowsNum - 1][cellInfo.colIndex].rowIndex,
              ut.indexTable[ut.rowsNum - 1][cellInfo.colIndex].cellIndex
            );
            if (evt.shiftKey && ut.selectedTds.length) {
              if (ut.selectedTds[0] !== endTdCol) {
                cellsRange = ut.getCellsRange(ut.selectedTds[0], endTdCol);
                ut.setSelected(cellsRange);
              } else {
                rng && rng.selectNodeContents(endTdCol).select();
              }
            } else {
              if (target !== endTdCol) {
                cellsRange = ut.getCellsRange(target, endTdCol);
                ut.setSelected(cellsRange);
              } else {
                rng && rng.selectNodeContents(endTdCol).select();
              }
            }
            return;
          }
          if (inTableSide(table, target, evt)) {
            var endTdRow = ut.getCell(
              ut.indexTable[cellInfo.rowIndex][ut.colsNum - 1].rowIndex,
              ut.indexTable[cellInfo.rowIndex][ut.colsNum - 1].cellIndex
            );
            if (evt.shiftKey && ut.selectedTds.length) {
              if (ut.selectedTds[0] !== endTdRow) {
                cellsRange = ut.getCellsRange(ut.selectedTds[0], endTdRow);
                ut.setSelected(cellsRange);
              } else {
                rng && rng.selectNodeContents(endTdRow).select();
              }
            } else {
              if (target !== endTdRow) {
                cellsRange = ut.getCellsRange(target, endTdRow);
                ut.setSelected(cellsRange);
              } else {
                rng && rng.selectNodeContents(endTdRow).select();
              }
            }
          }
        };
      });

      switchBorderColor(me, true);
    });

    domUtils.on(me.document, "mousemove", mouseMoveEvent);

    domUtils.on(me.document, "mouseout", function(evt) {
      var target = evt.target || evt.srcElement;
      if (target.tagName == "TABLE") {
        toggleDraggableState(me, false, "", null);
      }
    });
    /**
         * 表格隔行变色
         */
    me.addListener("interlacetable", function(type, table, classList) {
      if (!table) return;
      var me = this,
        rows = table.rows,
        len = rows.length,
        getClass = function(list, index, repeat) {
          return list[index]
            ? list[index]
            : repeat ? list[index % list.length] : "";
        };
      for (var i = 0; i < len; i++) {
        rows[i].className = getClass(
          classList || me.options.classList,
          i,
          true
        );
      }
    });
    me.addListener("uninterlacetable", function(type, table) {
      if (!table) return;
      var me = this,
        rows = table.rows,
        classList = me.options.classList,
        len = rows.length;
      for (var i = 0; i < len; i++) {
        domUtils.removeClasses(rows[i], classList);
      }
    });

    me.addListener("mousedown", mouseDownEvent);
    me.addListener("mouseup", mouseUpEvent);
    //拖动的时候触发mouseup
    domUtils.on(me.body, "dragstart", function(evt) {
      mouseUpEvent.call(me, "dragstart", evt);
    });
    me.addOutputRule(function(root) {
      utils.each(root.getNodesByTagName("div"), function(n) {
        if (n.getAttr("id") == "ue_tableDragLine") {
          n.parentNode.removeChild(n);
        }
      });
    });

    var currentRowIndex = 0;
    me.addListener("mousedown", function() {
      currentRowIndex = 0;
    });
    me.addListener("tabkeydown", function() {
      var range = this.selection.getRange(),
        common = range.getCommonAncestor(true, true),
        table = domUtils.findParentByTagName(common, "table");
      if (table) {
        if (domUtils.findParentByTagName(common, "caption", true)) {
          var cell = domUtils.getElementsByTagName(table, "th td");
          if (cell && cell.length) {
            range.setStart(cell[0], 0).setCursor(false, true);
          }
        } else {
          var cell = domUtils.findParentByTagName(common, ["td", "th"], true),
            ua = getUETable(cell);
          currentRowIndex = cell.rowSpan > 1
            ? currentRowIndex
            : ua.getCellInfo(cell).rowIndex;
          var nextCell = ua.getTabNextCell(cell, currentRowIndex);
          if (nextCell) {
            if (isEmptyBlock(nextCell)) {
              range.setStart(nextCell, 0).setCursor(false, true);
            } else {
              range.selectNodeContents(nextCell).select();
            }
          } else {
            me.fireEvent("saveScene");
            me.__hasEnterExecCommand = true;
            this.execCommand("insertrownext");
            me.__hasEnterExecCommand = false;
            range = this.selection.getRange();
            range
              .setStart(table.rows[table.rows.length - 1].cells[0], 0)
              .setCursor();
            me.fireEvent("saveScene");
          }
        }
        return true;
      }
    });
    browser.ie &&
      me.addListener("selectionchange", function() {
        toggleDraggableState(this, false, "", null);
      });
    me.addListener("keydown", function(type, evt) {
      var me = this;
      //处理在表格的最后一个输入tab产生新的表格
      var keyCode = evt.keyCode || evt.which;
      if (keyCode == 8 || keyCode == 46) {
        return;
      }
      var notCtrlKey =
        !evt.ctrlKey && !evt.metaKey && !evt.shiftKey && !evt.altKey;
      notCtrlKey &&
        removeSelectedClass(domUtils.getElementsByTagName(me.body, "td"));
      var ut = getUETableBySelected(me);
      if (!ut) return;
      notCtrlKey && ut.clearSelected();
    });

    me.addListener("beforegetcontent", function() {
      switchBorderColor(this, false);
      browser.ie &&
        utils.each(this.document.getElementsByTagName("caption"), function(ci) {
          if (domUtils.isEmptyNode(ci)) {
            ci.innerHTML = "&nbsp;";
          }
        });
    });
    me.addListener("aftergetcontent", function() {
      switchBorderColor(this, true);
    });
    me.addListener("getAllHtml", function() {
      removeSelectedClass(me.document.getElementsByTagName("td"));
    });
    //修正全屏状态下插入的表格宽度在非全屏状态下撑开编辑器的情况
    me.addListener("fullscreenchanged", function(type, fullscreen) {
      if (!fullscreen) {
        var ratio = this.body.offsetWidth / document.body.offsetWidth,
          tables = domUtils.getElementsByTagName(this.body, "table");
        utils.each(tables, function(table) {
          if (table.offsetWidth < me.body.offsetWidth) return false;
          var tds = domUtils.getElementsByTagName(table, "td"),
            backWidths = [];
          utils.each(tds, function(td) {
            backWidths.push(td.offsetWidth);
          });
          for (var i = 0, td; (td = tds[i]); i++) {
            td.setAttribute("width", Math.floor(backWidths[i] * ratio));
          }
          table.setAttribute(
            "width",
            Math.floor(getTableWidth(me, needIEHack, getDefaultValue(me)))
          );
        });
      }
    });

    //重写execCommand命令，用于处理框选时的处理
    var oldExecCommand = me.execCommand;
    me.execCommand = function(cmd, datatat) {
      var me = this,
        args = arguments;

      cmd = cmd.toLowerCase();
      var ut = getUETableBySelected(me),
        tds,
        range = new dom.Range(me.document),
        cmdFun = me.commands[cmd] || UE.commands[cmd],
        result;
      if (!cmdFun) return;
      if (
        ut &&
        !commands[cmd] &&
        !cmdFun.notNeedUndo &&
        !me.__hasEnterExecCommand
      ) {
        me.__hasEnterExecCommand = true;
        me.fireEvent("beforeexeccommand", cmd);
        tds = ut.selectedTds;
        var lastState = -2,
          lastValue = -2,
          value,
          state;
        for (var i = 0, td; (td = tds[i]); i++) {
          if (isEmptyBlock(td)) {
            range.setStart(td, 0).setCursor(false, true);
          } else {
            range.selectNode(td).select(true);
          }
          state = me.queryCommandState(cmd);
          value = me.queryCommandValue(cmd);
          if (state != -1) {
            if (lastState !== state || lastValue !== value) {
              me._ignoreContentChange = true;
              result = oldExecCommand.apply(me, arguments);
              me._ignoreContentChange = false;
            }
            lastState = me.queryCommandState(cmd);
            lastValue = me.queryCommandValue(cmd);
            if (domUtils.isEmptyBlock(td)) {
              domUtils.fillNode(me.document, td);
            }
          }
        }
        range.setStart(tds[0], 0).shrinkBoundary(true).setCursor(false, true);
        me.fireEvent("contentchange");
        me.fireEvent("afterexeccommand", cmd);
        me.__hasEnterExecCommand = false;
        me._selectionChange();
      } else {
        result = oldExecCommand.apply(me, arguments);
      }
      return result;
    };
  });
  /**
     * 删除obj的宽高style，改成属性宽高
     * @param obj
     * @param replaceToProperty
     */
  function removeStyleSize(obj, replaceToProperty) {
    removeStyle(obj, "width", true);
    removeStyle(obj, "height", true);
  }

  function removeStyle(obj, styleName, replaceToProperty) {
    if (obj.style[styleName]) {
      replaceToProperty &&
        obj.setAttribute(styleName, parseInt(obj.style[styleName], 10));
      obj.style[styleName] = "";
    }
  }

  function getParentTdOrTh(ele) {
    if (ele.tagName == "TD" || ele.tagName == "TH") return ele;
    var td;
    if (
      (td =
        domUtils.findParentByTagName(ele, "td", true) ||
        domUtils.findParentByTagName(ele, "th", true))
    )
      return td;
    return null;
  }

  function isEmptyBlock(node) {
    var reg = new RegExp(domUtils.fillChar, "g");
    if (
      node[browser.ie ? "innerText" : "textContent"]
        .replace(/^\s*$/, "")
        .replace(reg, "").length > 0
    ) {
      return 0;
    }
    for (var n in dtd.$isNotEmpty) {
      if (node.getElementsByTagName(n).length) {
        return 0;
      }
    }
    return 1;
  }

  function mouseCoords(evt) {
    if (evt.pageX || evt.pageY) {
      return { x: evt.pageX, y: evt.pageY };
    }
    return {
      x:
        evt.clientX + me.document.body.scrollLeft - me.document.body.clientLeft,
      y: evt.clientY + me.document.body.scrollTop - me.document.body.clientTop
    };
  }

  function mouseMoveEvent(evt) {
    if (isEditorDisabled()) {
      return;
    }

    try {
      //普通状态下鼠标移动
      var target = getParentTdOrTh(evt.target || evt.srcElement),
        pos;

      //区分用户的行为是拖动还是双击
      if (isInResizeBuffer) {
        me.body.style.webkitUserSelect = "none";

        if (
          Math.abs(userActionStatus.x - evt.clientX) > offsetOfTableCell ||
          Math.abs(userActionStatus.y - evt.clientY) > offsetOfTableCell
        ) {
          clearTableDragTimer();
          isInResizeBuffer = false;
          singleClickState = 0;
          //drag action
          tableBorderDrag(evt);
        }
      }

      //修改单元格大小时的鼠标移动
      if (onDrag && dragTd) {
        singleClickState = 0;
        me.body.style.webkitUserSelect = "none";
        me.selection.getNative()[
          browser.ie9below ? "empty" : "removeAllRanges"
        ]();
        pos = mouseCoords(evt);
        toggleDraggableState(me, true, onDrag, pos, target);
        if (onDrag == "h") {
          dragLine.style.left = getPermissionX(dragTd, evt) + "px";
        } else if (onDrag == "v") {
          dragLine.style.top = getPermissionY(dragTd, evt) + "px";
        }
        return;
      }
      //当鼠标处于table上时，修改移动过程中的光标状态
      if (target) {
        //针对使用table作为容器的组件不触发拖拽效果
        if (me.fireEvent("excludetable", target) === true) return;
        pos = mouseCoords(evt);
        var state = getRelation(target, pos),
          table = domUtils.findParentByTagName(target, "table", true);

        if (inTableSide(table, target, evt, true)) {
          if (me.fireEvent("excludetable", table) === true) return;
          me.body.style.cursor =
            "url(" + me.options.cursorpath + "h.png),pointer";
        } else if (inTableSide(table, target, evt)) {
          if (me.fireEvent("excludetable", table) === true) return;
          me.body.style.cursor =
            "url(" + me.options.cursorpath + "v.png),pointer";
        } else {
          me.body.style.cursor = "text";
          var curCell = target;
          if (/\d/.test(state)) {
            state = state.replace(/\d/, "");
            target = getUETable(target).getPreviewCell(target, state == "v");
          }
          //位于第一行的顶部或者第一列的左边时不可拖动
          toggleDraggableState(
            me,
            target ? !!state : false,
            target ? state : "",
            pos,
            target
          );
        }
      } else {
        toggleDragButton(false, table, me);
      }
    } catch (e) {
      showError(e);
    }
  }

  var dragButtonTimer;

  function toggleDragButton(show, table, editor) {
    if (!show) {
      if (dragOver) return;
      dragButtonTimer = setTimeout(function() {
        !dragOver &&
          dragButton &&
          dragButton.parentNode &&
          dragButton.parentNode.removeChild(dragButton);
      }, 2000);
    } else {
      createDragButton(table, editor);
    }
  }

  function createDragButton(table, editor) {
    var pos = domUtils.getXY(table),
      doc = table.ownerDocument;
    if (dragButton && dragButton.parentNode) return dragButton;
    dragButton = doc.createElement("div");
    dragButton.contentEditable = false;
    dragButton.innerHTML = "";
    dragButton.style.cssText =
      "width:15px;height:15px;background-image:url(" +
      editor.options.UEDITOR_HOME_URL +
      "dialogs/table/dragicon.png);position: absolute;cursor:move;top:" +
      (pos.y - 15) +
      "px;left:" +
      pos.x +
      "px;";
    domUtils.unSelectable(dragButton);
    dragButton.onmouseover = function(evt) {
      dragOver = true;
    };
    dragButton.onmouseout = function(evt) {
      dragOver = false;
    };
    domUtils.on(dragButton, "click", function(type, evt) {
      doClick(evt, this);
    });
    domUtils.on(dragButton, "dblclick", function(type, evt) {
      doDblClick(evt);
    });
    domUtils.on(dragButton, "dragstart", function(type, evt) {
      domUtils.preventDefault(evt);
    });
    var timer;

    function doClick(evt, button) {
      // 部分浏览器下需要清理
      clearTimeout(timer);
      timer = setTimeout(function() {
        editor.fireEvent("tableClicked", table, button);
      }, 300);
    }

    function doDblClick(evt) {
      clearTimeout(timer);
      var ut = getUETable(table),
        start = table.rows[0].cells[0],
        end = ut.getLastCell(),
        range = ut.getCellsRange(start, end);
      editor.selection.getRange().setStart(start, 0).setCursor(false, true);
      ut.setSelected(range);
    }

    doc.body.appendChild(dragButton);
  }

  //    function inPosition(table, pos) {
  //        var tablePos = domUtils.getXY(table),
  //            width = table.offsetWidth,
  //            height = table.offsetHeight;
  //        if (pos.x - tablePos.x < 5 && pos.y - tablePos.y < 5) {
  //            return "topLeft";
  //        } else if (tablePos.x + width - pos.x < 5 && tablePos.y + height - pos.y < 5) {
  //            return "bottomRight";
  //        }
  //    }

  function inTableSide(table, cell, evt, top) {
    var pos = mouseCoords(evt),
      state = getRelation(cell, pos);

    if (top) {
      var caption = table.getElementsByTagName("caption")[0],
        capHeight = caption ? caption.offsetHeight : 0;
      return state == "v1" && pos.y - domUtils.getXY(table).y - capHeight < 8;
    } else {
      return state == "h1" && pos.x - domUtils.getXY(table).x < 8;
    }
  }

  /**
     * 获取拖动时允许的X轴坐标
     * @param dragTd
     * @param evt
     */
  function getPermissionX(dragTd, evt) {
    var ut = getUETable(dragTd);
    if (ut) {
      var preTd = ut.getSameEndPosCells(dragTd, "x")[0],
        nextTd = ut.getSameStartPosXCells(dragTd)[0],
        mouseX = mouseCoords(evt).x,
        left =
          (preTd ? domUtils.getXY(preTd).x : domUtils.getXY(ut.table).x) + 20,
        right = nextTd
          ? domUtils.getXY(nextTd).x + nextTd.offsetWidth - 20
          : me.body.offsetWidth + 5 ||
              parseInt(domUtils.getComputedStyle(me.body, "width"), 10);

      left += cellMinWidth;
      right -= cellMinWidth;

      return mouseX < left ? left : mouseX > right ? right : mouseX;
    }
  }

  /**
     * 获取拖动时允许的Y轴坐标
     */
  function getPermissionY(dragTd, evt) {
    try {
      var top = domUtils.getXY(dragTd).y,
        mousePosY = mouseCoords(evt).y;
      return mousePosY < top ? top : mousePosY;
    } catch (e) {
      showError(e);
    }
  }

  /**
     * 移动状态切换
     */
  function toggleDraggableState(editor, draggable, dir, mousePos, cell) {
    try {
      editor.body.style.cursor = dir == "h"
        ? "col-resize"
        : dir == "v" ? "row-resize" : "text";
      if (browser.ie) {
        if (dir && !mousedown && !getUETableBySelected(editor)) {
          getDragLine(editor, editor.document);
          showDragLineAt(dir, cell);
        } else {
          hideDragLine(editor);
        }
      }
      onBorder = draggable;
    } catch (e) {
      showError(e);
    }
  }

  /**
     * 获取与UETable相关的resize line
     * @param uetable UETable对象
     */
  function getResizeLineByUETable() {
    var lineId = "_UETableResizeLine",
      line = this.document.getElementById(lineId);

    if (!line) {
      line = this.document.createElement("div");
      line.id = lineId;
      line.contnetEditable = false;
      line.setAttribute("unselectable", "on");

      var styles = {
        width: 2 * cellBorderWidth + 1 + "px",
        position: "absolute",
        "z-index": 100000,
        cursor: "col-resize",
        background: "red",
        display: "none"
      };

      //切换状态
      line.onmouseout = function() {
        this.style.display = "none";
      };

      utils.extend(line.style, styles);

      this.document.body.appendChild(line);
    }

    return line;
  }

  /**
     * 更新resize-line
     */
  function updateResizeLine(cell, uetable) {
    var line = getResizeLineByUETable.call(this),
      table = uetable.table,
      styles = {
        top: domUtils.getXY(table).y + "px",
        left:
          domUtils.getXY(cell).x + cell.offsetWidth - cellBorderWidth + "px",
        display: "block",
        height: table.offsetHeight + "px"
      };

    utils.extend(line.style, styles);
  }

  /**
     * 显示resize-line
     */
  function showResizeLine(cell) {
    var uetable = getUETable(cell);

    updateResizeLine.call(this, cell, uetable);
  }

  /**
     * 获取鼠标与当前单元格的相对位置
     * @param ele
     * @param mousePos
     */
  function getRelation(ele, mousePos) {
    var elePos = domUtils.getXY(ele);

    if (!elePos) {
      return "";
    }

    if (elePos.x + ele.offsetWidth - mousePos.x < cellBorderWidth) {
      return "h";
    }
    if (mousePos.x - elePos.x < cellBorderWidth) {
      return "h1";
    }
    if (elePos.y + ele.offsetHeight - mousePos.y < cellBorderWidth) {
      return "v";
    }
    if (mousePos.y - elePos.y < cellBorderWidth) {
      return "v1";
    }
    return "";
  }

  function mouseDownEvent(type, evt) {
    if (isEditorDisabled()) {
      return;
    }

    userActionStatus = {
      x: evt.clientX,
      y: evt.clientY
    };

    //右键菜单单独处理
    if (evt.button == 2) {
      var ut = getUETableBySelected(me),
        flag = false;

      if (ut) {
        var td = getTargetTd(me, evt);
        utils.each(ut.selectedTds, function(ti) {
          if (ti === td) {
            flag = true;
          }
        });
        if (!flag) {
          removeSelectedClass(domUtils.getElementsByTagName(me.body, "th td"));
          ut.clearSelected();
        } else {
          td = ut.selectedTds[0];
          setTimeout(function() {
            me.selection.getRange().setStart(td, 0).setCursor(false, true);
          }, 0);
        }
      }
    } else {
      tableClickHander(evt);
    }
  }

  //清除表格的计时器
  function clearTableTimer() {
    tabTimer && clearTimeout(tabTimer);
    tabTimer = null;
  }

  //双击收缩
  function tableDbclickHandler(evt) {
    singleClickState = 0;
    evt = evt || me.window.event;
    var target = getParentTdOrTh(evt.target || evt.srcElement);
    if (target) {
      var h;
      if ((h = getRelation(target, mouseCoords(evt)))) {
        hideDragLine(me);

        if (h == "h1") {
          h = "h";
          if (
            inTableSide(
              domUtils.findParentByTagName(target, "table"),
              target,
              evt
            )
          ) {
            me.execCommand("adaptbywindow");
          } else {
            target = getUETable(target).getPreviewCell(target);
            if (target) {
              var rng = me.selection.getRange();
              rng.selectNodeContents(target).setCursor(true, true);
            }
          }
        }
        if (h == "h") {
          var ut = getUETable(target),
            table = ut.table,
            cells = getCellsByMoveBorder(target, table, true);

          cells = extractArray(cells, "left");

          ut.width = ut.offsetWidth;

          var oldWidth = [],
            newWidth = [];

          utils.each(cells, function(cell) {
            oldWidth.push(cell.offsetWidth);
          });

          utils.each(cells, function(cell) {
            cell.removeAttribute("width");
          });

          window.setTimeout(function() {
            //是否允许改变
            var changeable = true;

            utils.each(cells, function(cell, index) {
              var width = cell.offsetWidth;

              if (width > oldWidth[index]) {
                changeable = false;
                return false;
              }

              newWidth.push(width);
            });

            var change = changeable ? newWidth : oldWidth;

            utils.each(cells, function(cell, index) {
              cell.width = change[index] - getTabcellSpace();
            });
          }, 0);

          //                    minWidth -= cellMinWidth;
          //
          //                    table.removeAttribute("width");
          //                    utils.each(cells, function (cell) {
          //                        cell.style.width = "";
          //                        cell.width -= minWidth;
          //                    });
        }
      }
    }
  }

  function tableClickHander(evt) {
    removeSelectedClass(domUtils.getElementsByTagName(me.body, "td th"));
    //trace:3113
    //选中单元格，点击table外部，不会清掉table上挂的ueTable,会引起getUETableBySelected方法返回值
    utils.each(me.document.getElementsByTagName("table"), function(t) {
      t.ueTable = null;
    });
    startTd = getTargetTd(me, evt);
    if (!startTd) return;
    var table = domUtils.findParentByTagName(startTd, "table", true);
    ut = getUETable(table);
    ut && ut.clearSelected();

    //判断当前鼠标状态
    if (!onBorder) {
      me.document.body.style.webkitUserSelect = "";
      mousedown = true;
      me.addListener("mouseover", mouseOverEvent);
    } else {
      //边框上的动作处理
      borderActionHandler(evt);
    }
  }

  //处理表格边框上的动作, 这里做延时处理，避免两种动作互相影响
  function borderActionHandler(evt) {
    if (browser.ie) {
      evt = reconstruct(evt);
    }

    clearTableDragTimer();

    //是否正在等待resize的缓冲中
    isInResizeBuffer = true;

    tableDragTimer = setTimeout(function() {
      tableBorderDrag(evt);
    }, dblclickTime);
  }

  function extractArray(originArr, key) {
    var result = [],
      tmp = null;

    for (var i = 0, len = originArr.length; i < len; i++) {
      tmp = originArr[i][key];

      if (tmp) {
        result.push(tmp);
      }
    }

    return result;
  }

  function clearTableDragTimer() {
    tableDragTimer && clearTimeout(tableDragTimer);
    tableDragTimer = null;
  }

  function reconstruct(obj) {
    var attrs = [
      "pageX",
      "pageY",
      "clientX",
      "clientY",
      "srcElement",
      "target"
    ],
      newObj = {};

    if (obj) {
      for (var i = 0, key, val; (key = attrs[i]); i++) {
        val = obj[key];
        val && (newObj[key] = val);
      }
    }

    return newObj;
  }

  //边框拖动
  function tableBorderDrag(evt) {
    isInResizeBuffer = false;

    startTd = evt.target || evt.srcElement;
    if (!startTd) return;
    var state = getRelation(startTd, mouseCoords(evt));
    if (/\d/.test(state)) {
      state = state.replace(/\d/, "");
      startTd = getUETable(startTd).getPreviewCell(startTd, state == "v");
    }
    hideDragLine(me);
    getDragLine(me, me.document);
    me.fireEvent("saveScene");
    showDragLineAt(state, startTd);
    mousedown = true;
    //拖动开始
    onDrag = state;
    dragTd = startTd;
  }

  function mouseUpEvent(type, evt) {
    if (isEditorDisabled()) {
      return;
    }

    clearTableDragTimer();

    isInResizeBuffer = false;

    if (onBorder) {
      singleClickState = ++singleClickState % 3;

      userActionStatus = {
        x: evt.clientX,
        y: evt.clientY
      };

      tableResizeTimer = setTimeout(function() {
        singleClickState > 0 && singleClickState--;
      }, dblclickTime);

      if (singleClickState === 2) {
        singleClickState = 0;
        tableDbclickHandler(evt);
        return;
      }
    }

    if (evt.button == 2) return;
    var me = this;
    //清除表格上原生跨选问题
    var range = me.selection.getRange(),
      start = domUtils.findParentByTagName(range.startContainer, "table", true),
      end = domUtils.findParentByTagName(range.endContainer, "table", true);

    if (start || end) {
      if (start === end) {
        start = domUtils.findParentByTagName(
          range.startContainer,
          ["td", "th", "caption"],
          true
        );
        end = domUtils.findParentByTagName(
          range.endContainer,
          ["td", "th", "caption"],
          true
        );
        if (start !== end) {
          me.selection.clearRange();
        }
      } else {
        me.selection.clearRange();
      }
    }
    mousedown = false;
    me.document.body.style.webkitUserSelect = "";
    //拖拽状态下的mouseUP
    if (onDrag && dragTd) {
      me.selection.getNative()[
        browser.ie9below ? "empty" : "removeAllRanges"
      ]();

      singleClickState = 0;
      dragLine = me.document.getElementById("ue_tableDragLine");

      // trace 3973
      if (dragLine) {
        var dragTdPos = domUtils.getXY(dragTd),
          dragLinePos = domUtils.getXY(dragLine);

        switch (onDrag) {
          case "h":
            changeColWidth(dragTd, dragLinePos.x - dragTdPos.x);
            break;
          case "v":
            changeRowHeight(
              dragTd,
              dragLinePos.y - dragTdPos.y - dragTd.offsetHeight
            );
            break;
          default:
        }
        onDrag = "";
        dragTd = null;

        hideDragLine(me);
        me.fireEvent("saveScene");
        return;
      }
    }
    //正常状态下的mouseup
    if (!startTd) {
      var target = domUtils.findParentByTagName(
        evt.target || evt.srcElement,
        "td",
        true
      );
      if (!target)
        target = domUtils.findParentByTagName(
          evt.target || evt.srcElement,
          "th",
          true
        );
      if (target && (target.tagName == "TD" || target.tagName == "TH")) {
        if (me.fireEvent("excludetable", target) === true) return;
        range = new dom.Range(me.document);
        range.setStart(target, 0).setCursor(false, true);
      }
    } else {
      var ut = getUETable(startTd),
        cell = ut ? ut.selectedTds[0] : null;
      if (cell) {
        range = new dom.Range(me.document);
        if (domUtils.isEmptyBlock(cell)) {
          range.setStart(cell, 0).setCursor(false, true);
        } else {
          range
            .selectNodeContents(cell)
            .shrinkBoundary()
            .setCursor(false, true);
        }
      } else {
        range = me.selection.getRange().shrinkBoundary();
        if (!range.collapsed) {
          var start = domUtils.findParentByTagName(
            range.startContainer,
            ["td", "th"],
            true
          ),
            end = domUtils.findParentByTagName(
              range.endContainer,
              ["td", "th"],
              true
            );
          //在table里边的不能清除
          if (
            (start && !end) ||
            (!start && end) ||
            (start && end && start !== end)
          ) {
            range.setCursor(false, true);
          }
        }
      }
      startTd = null;
      me.removeListener("mouseover", mouseOverEvent);
    }
    me._selectionChange(250, evt);
  }

  function mouseOverEvent(type, evt) {
    if (isEditorDisabled()) {
      return;
    }

    var me = this,
      tar = evt.target || evt.srcElement;
    currentTd =
      domUtils.findParentByTagName(tar, "td", true) ||
      domUtils.findParentByTagName(tar, "th", true);
    //需要判断两个TD是否位于同一个表格内
    if (
      startTd &&
      currentTd &&
      ((startTd.tagName == "TD" && currentTd.tagName == "TD") ||
        (startTd.tagName == "TH" && currentTd.tagName == "TH")) &&
      domUtils.findParentByTagName(startTd, "table") ==
        domUtils.findParentByTagName(currentTd, "table")
    ) {
      var ut = getUETable(currentTd);
      if (startTd != currentTd) {
        me.document.body.style.webkitUserSelect = "none";
        me.selection.getNative()[
          browser.ie9below ? "empty" : "removeAllRanges"
        ]();
        var range = ut.getCellsRange(startTd, currentTd);
        ut.setSelected(range);
      } else {
        me.document.body.style.webkitUserSelect = "";
        ut.clearSelected();
      }
    }
    evt.preventDefault ? evt.preventDefault() : (evt.returnValue = false);
  }

  function setCellHeight(cell, height, backHeight) {
    var lineHight = parseInt(
      domUtils.getComputedStyle(cell, "line-height"),
      10
    ),
      tmpHeight = backHeight + height;
    height = tmpHeight < lineHight ? lineHight : tmpHeight;
    if (cell.style.height) cell.style.height = "";
    cell.rowSpan == 1
      ? cell.setAttribute("height", height)
      : cell.removeAttribute && cell.removeAttribute("height");
  }

  function getWidth(cell) {
    if (!cell) return 0;
    return parseInt(domUtils.getComputedStyle(cell, "width"), 10);
  }

  function changeColWidth(cell, changeValue) {
    var ut = getUETable(cell);
    if (ut) {
      //根据当前移动的边框获取相关的单元格
      var table = ut.table,
        cells = getCellsByMoveBorder(cell, table);

      table.style.width = "";
      table.removeAttribute("width");

      //修正改变量
      changeValue = correctChangeValue(changeValue, cell, cells);

      if (cell.nextSibling) {
        var i = 0;

        utils.each(cells, function(cellGroup) {
          cellGroup.left.width = +cellGroup.left.width + changeValue;
          cellGroup.right &&
            (cellGroup.right.width = +cellGroup.right.width - changeValue);
        });
      } else {
        utils.each(cells, function(cellGroup) {
          cellGroup.left.width -= -changeValue;
        });
      }
    }
  }

  function isEditorDisabled() {
    return me.body.contentEditable === "false";
  }

  function changeRowHeight(td, changeValue) {
    if (Math.abs(changeValue) < 10) return;
    var ut = getUETable(td);
    if (ut) {
      var cells = ut.getSameEndPosCells(td, "y"),
        //备份需要连带变化的td的原始高度，否则后期无法获取正确的值
        backHeight = cells[0] ? cells[0].offsetHeight : 0;
      for (var i = 0, cell; (cell = cells[i++]); ) {
        setCellHeight(cell, changeValue, backHeight);
      }
    }
  }

  /**
     * 获取调整单元格大小的相关单元格
     * @isContainMergeCell 返回的结果中是否包含发生合并后的单元格
     */
  function getCellsByMoveBorder(cell, table, isContainMergeCell) {
    if (!table) {
      table = domUtils.findParentByTagName(cell, "table");
    }

    if (!table) {
      return null;
    }

    //获取到该单元格所在行的序列号
    var index = domUtils.getNodeIndex(cell),
      temp = cell,
      rows = table.rows,
      colIndex = 0;

    while (temp) {
      //获取到当前单元格在未发生单元格合并时的序列
      if (temp.nodeType === 1) {
        colIndex += temp.colSpan || 1;
      }
      temp = temp.previousSibling;
    }

    temp = null;

    //记录想关的单元格
    var borderCells = [];

    utils.each(rows, function(tabRow) {
      var cells = tabRow.cells,
        currIndex = 0;

      utils.each(cells, function(tabCell) {
        currIndex += tabCell.colSpan || 1;

        if (currIndex === colIndex) {
          borderCells.push({
            left: tabCell,
            right: tabCell.nextSibling || null
          });

          return false;
        } else if (currIndex > colIndex) {
          if (isContainMergeCell) {
            borderCells.push({
              left: tabCell
            });
          }

          return false;
        }
      });
    });

    return borderCells;
  }

  /**
     * 通过给定的单元格集合获取最小的单元格width
     */
  function getMinWidthByTableCells(cells) {
    var minWidth = Number.MAX_VALUE;

    for (var i = 0, curCell; (curCell = cells[i]); i++) {
      minWidth = Math.min(
        minWidth,
        curCell.width || getTableCellWidth(curCell)
      );
    }

    return minWidth;
  }

  function correctChangeValue(changeValue, relatedCell, cells) {
    //为单元格的paading预留空间
    changeValue -= getTabcellSpace();

    if (changeValue < 0) {
      return 0;
    }

    changeValue -= getTableCellWidth(relatedCell);

    //确定方向
    var direction = changeValue < 0 ? "left" : "right";

    changeValue = Math.abs(changeValue);

    //只关心非最后一个单元格就可以
    utils.each(cells, function(cellGroup) {
      var curCell = cellGroup[direction];

      //为单元格保留最小空间
      if (curCell) {
        changeValue = Math.min(
          changeValue,
          getTableCellWidth(curCell) - cellMinWidth
        );
      }
    });

    //修正越界
    changeValue = changeValue < 0 ? 0 : changeValue;

    return direction === "left" ? -changeValue : changeValue;
  }

  function getTableCellWidth(cell) {
    var width = 0,
      //偏移纠正量
      offset = 0,
      width = cell.offsetWidth - getTabcellSpace();

    //最后一个节点纠正一下
    if (!cell.nextSibling) {
      width -= getTableCellOffset(cell);
    }

    width = width < 0 ? 0 : width;

    try {
      cell.width = width;
    } catch (e) {}

    return width;
  }

  /**
     * 获取单元格所在表格的最末单元格的偏移量
     */
  function getTableCellOffset(cell) {
    tab = domUtils.findParentByTagName(cell, "table", false);

    if (tab.offsetVal === undefined) {
      var prev = cell.previousSibling;

      if (prev) {
        //最后一个单元格和前一个单元格的width diff结果 如果恰好为一个border width， 则条件成立
        tab.offsetVal = cell.offsetWidth - prev.offsetWidth === UT.borderWidth
          ? UT.borderWidth
          : 0;
      } else {
        tab.offsetVal = 0;
      }
    }

    return tab.offsetVal;
  }

  function getTabcellSpace() {
    if (UT.tabcellSpace === undefined) {
      var cell = null,
        tab = me.document.createElement("table"),
        tbody = me.document.createElement("tbody"),
        trow = me.document.createElement("tr"),
        tabcell = me.document.createElement("td"),
        mirror = null;

      tabcell.style.cssText = "border: 0;";
      tabcell.width = 1;

      trow.appendChild(tabcell);
      trow.appendChild((mirror = tabcell.cloneNode(false)));

      tbody.appendChild(trow);

      tab.appendChild(tbody);

      tab.style.cssText = "visibility: hidden;";

      me.body.appendChild(tab);

      UT.paddingSpace = tabcell.offsetWidth - 1;

      var tmpTabWidth = tab.offsetWidth;

      tabcell.style.cssText = "";
      mirror.style.cssText = "";

      UT.borderWidth = (tab.offsetWidth - tmpTabWidth) / 3;

      UT.tabcellSpace = UT.paddingSpace + UT.borderWidth;

      me.body.removeChild(tab);
    }

    getTabcellSpace = function() {
      return UT.tabcellSpace;
    };

    return UT.tabcellSpace;
  }

  function getDragLine(editor, doc) {
    if (mousedown) return;
    dragLine = editor.document.createElement("div");
    domUtils.setAttributes(dragLine, {
      id: "ue_tableDragLine",
      unselectable: "on",
      contenteditable: false,
      onresizestart: "return false",
      ondragstart: "return false",
      onselectstart: "return false",
      style:
        "background-color:blue;position:absolute;padding:0;margin:0;background-image:none;border:0px none;opacity:0;filter:alpha(opacity=0)"
    });
    editor.body.appendChild(dragLine);
  }

  function hideDragLine(editor) {
    if (mousedown) return;
    var line;
    while ((line = editor.document.getElementById("ue_tableDragLine"))) {
      domUtils.remove(line);
    }
  }

  /**
     * 依据state（v|h）在cell位置显示横线
     * @param state
     * @param cell
     */
  function showDragLineAt(state, cell) {
    if (!cell) return;
    var table = domUtils.findParentByTagName(cell, "table"),
      caption = table.getElementsByTagName("caption"),
      width = table.offsetWidth,
      height =
        table.offsetHeight - (caption.length > 0 ? caption[0].offsetHeight : 0),
      tablePos = domUtils.getXY(table),
      cellPos = domUtils.getXY(cell),
      css;
    switch (state) {
      case "h":
        css =
          "height:" +
          height +
          "px;top:" +
          (tablePos.y + (caption.length > 0 ? caption[0].offsetHeight : 0)) +
          "px;left:" +
          (cellPos.x + cell.offsetWidth);
        dragLine.style.cssText =
          css +
          "px;position: absolute;display:block;background-color:blue;width:1px;border:0; color:blue;opacity:.3;filter:alpha(opacity=30)";
        break;
      case "v":
        css =
          "width:" +
          width +
          "px;left:" +
          tablePos.x +
          "px;top:" +
          (cellPos.y + cell.offsetHeight);
        //必须加上border:0和color:blue，否则低版ie不支持背景色显示
        dragLine.style.cssText =
          css +
          "px;overflow:hidden;position: absolute;display:block;background-color:blue;height:1px;border:0;color:blue;opacity:.2;filter:alpha(opacity=20)";
        break;
      default:
    }
  }

  /**
     * 当表格边框颜色为白色时设置为虚线,true为添加虚线
     * @param editor
     * @param flag
     */
  function switchBorderColor(editor, flag) {
    var tableArr = domUtils.getElementsByTagName(editor.body, "table"),
      color;
    for (var i = 0, node; (node = tableArr[i++]); ) {
      var td = domUtils.getElementsByTagName(node, "td");
      if (td[0]) {
        if (flag) {
          color = td[0].style.borderColor.replace(/\s/g, "");
          if (/(#ffffff)|(rgb\(255,255,255\))/gi.test(color))
            domUtils.addClass(node, "noBorderTable");
        } else {
          domUtils.removeClasses(node, "noBorderTable");
        }
      }
    }
  }

  function getTableWidth(editor, needIEHack, defaultValue) {
    var body = editor.body;
    return (
      body.offsetWidth -
      (needIEHack
        ? parseInt(domUtils.getComputedStyle(body, "margin-left"), 10) * 2
        : 0) -
      defaultValue.tableBorder * 2 -
      (editor.options.offsetWidth || 0)
    );
  }

  /**
     * 获取当前拖动的单元格
     */
  function getTargetTd(editor, evt) {
    var target = domUtils.findParentByTagName(
      evt.target || evt.srcElement,
      ["td", "th"],
      true
    ),
      dir = null;

    if (!target) {
      return null;
    }

    dir = getRelation(target, mouseCoords(evt));

    //如果有前一个节点， 需要做一个修正， 否则可能会得到一个错误的td

    if (!target) {
      return null;
    }

    if (dir === "h1" && target.previousSibling) {
      var position = domUtils.getXY(target),
        cellWidth = target.offsetWidth;

      if (Math.abs(position.x + cellWidth - evt.clientX) > cellWidth / 3) {
        target = target.previousSibling;
      }
    } else if (dir === "v1" && target.parentNode.previousSibling) {
      var position = domUtils.getXY(target),
        cellHeight = target.offsetHeight;

      if (Math.abs(position.y + cellHeight - evt.clientY) > cellHeight / 3) {
        target = target.parentNode.previousSibling.firstChild;
      }
    }

    //排除了非td内部以及用于代码高亮部分的td
    return target && !(editor.fireEvent("excludetable", target) === true)
      ? target
      : null;
  }
};


// plugins/table.sort.js
/**
 * Created with JetBrains PhpStorm.
 * User: Jinqn
 * Date: 13-10-12
 * Time: 上午10:20
 * To change this template use File | Settings | File Templates.
 */

UE.UETable.prototype.sortTable = function(sortByCellIndex, compareFn) {
  var table = this.table,
    rows = table.rows,
    trArray = [],
    flag = rows[0].cells[0].tagName === "TH",
    lastRowIndex = 0;
  if (this.selectedTds.length) {
    var range = this.cellsRange,
      len = range.endRowIndex + 1;
    for (var i = range.beginRowIndex; i < len; i++) {
      trArray[i] = rows[i];
    }
    trArray.splice(0, range.beginRowIndex);
    lastRowIndex = range.endRowIndex + 1 === this.rowsNum
      ? 0
      : range.endRowIndex + 1;
  } else {
    for (var i = 0, len = rows.length; i < len; i++) {
      trArray[i] = rows[i];
    }
  }

  var Fn = {
    reversecurrent: function(td1, td2) {
      return 1;
    },
    orderbyasc: function(td1, td2) {
      var value1 = td1.innerText || td1.textContent,
        value2 = td2.innerText || td2.textContent;
      return value1.localeCompare(value2);
    },
    reversebyasc: function(td1, td2) {
      var value1 = td1.innerHTML,
        value2 = td2.innerHTML;
      return value2.localeCompare(value1);
    },
    orderbynum: function(td1, td2) {
      var value1 = td1[browser.ie ? "innerText" : "textContent"].match(/\d+/),
        value2 = td2[browser.ie ? "innerText" : "textContent"].match(/\d+/);
      if (value1) value1 = +value1[0];
      if (value2) value2 = +value2[0];
      return (value1 || 0) - (value2 || 0);
    },
    reversebynum: function(td1, td2) {
      var value1 = td1[browser.ie ? "innerText" : "textContent"].match(/\d+/),
        value2 = td2[browser.ie ? "innerText" : "textContent"].match(/\d+/);
      if (value1) value1 = +value1[0];
      if (value2) value2 = +value2[0];
      return (value2 || 0) - (value1 || 0);
    }
  };

  //对表格设置排序的标记data-sort-type
  table.setAttribute(
    "data-sort-type",
    compareFn && typeof compareFn === "string" && Fn[compareFn] ? compareFn : ""
  );

  //th不参与排序
  flag && trArray.splice(0, 1);
  trArray = utils.sort(trArray, function(tr1, tr2) {
    var result;
    if (compareFn && typeof compareFn === "function") {
      result = compareFn.call(
        this,
        tr1.cells[sortByCellIndex],
        tr2.cells[sortByCellIndex]
      );
    } else if (compareFn && typeof compareFn === "number") {
      result = 1;
    } else if (compareFn && typeof compareFn === "string" && Fn[compareFn]) {
      result = Fn[compareFn].call(
        this,
        tr1.cells[sortByCellIndex],
        tr2.cells[sortByCellIndex]
      );
    } else {
      result = Fn["orderbyasc"].call(
        this,
        tr1.cells[sortByCellIndex],
        tr2.cells[sortByCellIndex]
      );
    }
    return result;
  });
  var fragment = table.ownerDocument.createDocumentFragment();
  for (var j = 0, len = trArray.length; j < len; j++) {
    fragment.appendChild(trArray[j]);
  }
  var tbody = table.getElementsByTagName("tbody")[0];
  if (!lastRowIndex) {
    tbody.appendChild(fragment);
  } else {
    tbody.insertBefore(
      fragment,
      rows[lastRowIndex - range.endRowIndex + range.beginRowIndex - 1]
    );
  }
};

UE.plugins["tablesort"] = function() {
  var me = this,
    UT = UE.UETable,
    getUETable = function(tdOrTable) {
      return UT.getUETable(tdOrTable);
    },
    getTableItemsByRange = function(editor) {
      return UT.getTableItemsByRange(editor);
    };

  me.ready(function() {
    //添加表格可排序的样式
    utils.cssRule(
      "tablesort",
      "table.sortEnabled tr.firstRow th,table.sortEnabled tr.firstRow td{padding-right:20px;background-repeat: no-repeat;background-position: center right;" +
        "   background-image:url(" +
        me.options.themePath +
        me.options.theme +
        "/images/sortable.png);}",
      me.document
    );

    //做单元格合并操作时,清除可排序标识
    me.addListener("afterexeccommand", function(type, cmd) {
      if (cmd == "mergeright" || cmd == "mergedown" || cmd == "mergecells") {
        this.execCommand("disablesort");
      }
    });
  });

  //表格排序
  UE.commands["sorttable"] = {
    queryCommandState: function() {
      var me = this,
        tableItems = getTableItemsByRange(me);
      if (!tableItems.cell) return -1;
      var table = tableItems.table,
        cells = table.getElementsByTagName("td");
      for (var i = 0, cell; (cell = cells[i++]); ) {
        if (cell.rowSpan != 1 || cell.colSpan != 1) return -1;
      }
      return 0;
    },
    execCommand: function(cmd, fn) {
      var me = this,
        range = me.selection.getRange(),
        bk = range.createBookmark(true),
        tableItems = getTableItemsByRange(me),
        cell = tableItems.cell,
        ut = getUETable(tableItems.table),
        cellInfo = ut.getCellInfo(cell);
      ut.sortTable(cellInfo.cellIndex, fn);
      range.moveToBookmark(bk);
      try {
        range.select();
      } catch (e) {}
    }
  };

  //设置表格可排序,清除表格可排序
  UE.commands["enablesort"] = UE.commands["disablesort"] = {
    queryCommandState: function(cmd) {
      var table = getTableItemsByRange(this).table;
      if (table && cmd == "enablesort") {
        var cells = domUtils.getElementsByTagName(table, "th td");
        for (var i = 0; i < cells.length; i++) {
          if (
            cells[i].getAttribute("colspan") > 1 ||
            cells[i].getAttribute("rowspan") > 1
          )
            return -1;
        }
      }

      return !table
        ? -1
        : (cmd == "enablesort") ^
            (table.getAttribute("data-sort") != "sortEnabled")
          ? -1
          : 0;
    },
    execCommand: function(cmd) {
      var table = getTableItemsByRange(this).table;
      table.setAttribute(
        "data-sort",
        cmd == "enablesort" ? "sortEnabled" : "sortDisabled"
      );
      cmd == "enablesort"
        ? domUtils.addClass(table, "sortEnabled")
        : domUtils.removeClasses(table, "sortEnabled");
    }
  };
};


// plugins/contextmenu.js
///import core
///commands 右键菜单
///commandsName  ContextMenu
///commandsTitle  右键菜单
/**
 * 右键菜单
 * @function
 * @name baidu.editor.plugins.contextmenu
 * @author zhanyi
 */

UE.plugins["contextmenu"] = function() {
  var me = this;

  me.setOpt("enableContextMenu", me.getOpt("enableContextMenu") || true);

  if (me.getOpt("enableContextMenu") === false) {
    return;
  }
  var lang = me.getLang("contextMenu"),
    menu,
    items = me.options.contextMenu || [
      { label: lang["selectall"], cmdName: "selectall" },
      {
        label: lang.cleardoc,
        cmdName: "cleardoc",
        exec: function() {
          if (confirm(lang.confirmclear)) {
            this.execCommand("cleardoc");
          }
        }
      },
      "-",
      {
        label: lang.unlink,
        cmdName: "unlink"
      },
      "-",
      {
        group: lang.paragraph,
        icon: "justifyjustify",
        subMenu: [
          {
            label: lang.justifyleft,
            cmdName: "justify",
            value: "left"
          },
          {
            label: lang.justifyright,
            cmdName: "justify",
            value: "right"
          },
          {
            label: lang.justifycenter,
            cmdName: "justify",
            value: "center"
          },
          {
            label: lang.justifyjustify,
            cmdName: "justify",
            value: "justify"
          }
        ]
      },
      "-",
      {
        group: lang.table,
        icon: "table",
        subMenu: [
          {
            label: lang.inserttable,
            cmdName: "inserttable"
          },
          {
            label: lang.deletetable,
            cmdName: "deletetable"
          },
          "-",
          {
            label: lang.deleterow,
            cmdName: "deleterow"
          },
          {
            label: lang.deletecol,
            cmdName: "deletecol"
          },
          {
            label: lang.insertcol,
            cmdName: "insertcol"
          },
          {
            label: lang.insertcolnext,
            cmdName: "insertcolnext"
          },
          {
            label: lang.insertrow,
            cmdName: "insertrow"
          },
          {
            label: lang.insertrownext,
            cmdName: "insertrownext"
          },
          "-",
          {
            label: lang.insertcaption,
            cmdName: "insertcaption"
          },
          {
            label: lang.deletecaption,
            cmdName: "deletecaption"
          },
          {
            label: lang.inserttitle,
            cmdName: "inserttitle"
          },
          {
            label: lang.deletetitle,
            cmdName: "deletetitle"
          },
          {
            label: lang.inserttitlecol,
            cmdName: "inserttitlecol"
          },
          {
            label: lang.deletetitlecol,
            cmdName: "deletetitlecol"
          },
          "-",
          {
            label: lang.mergecells,
            cmdName: "mergecells"
          },
          {
            label: lang.mergeright,
            cmdName: "mergeright"
          },
          {
            label: lang.mergedown,
            cmdName: "mergedown"
          },
          "-",
          {
            label: lang.splittorows,
            cmdName: "splittorows"
          },
          {
            label: lang.splittocols,
            cmdName: "splittocols"
          },
          {
            label: lang.splittocells,
            cmdName: "splittocells"
          },
          "-",
          {
            label: lang.averageDiseRow,
            cmdName: "averagedistributerow"
          },
          {
            label: lang.averageDisCol,
            cmdName: "averagedistributecol"
          },
          "-",
          {
            label: lang.edittd,
            cmdName: "edittd",
            exec: function() {
              if (UE.ui["edittd"]) {
                new UE.ui["edittd"](this);
              }
              this.getDialog("edittd").open();
            }
          },
          {
            label: lang.edittable,
            cmdName: "edittable",
            exec: function() {
              if (UE.ui["edittable"]) {
                new UE.ui["edittable"](this);
              }
              this.getDialog("edittable").open();
            }
          },
          {
            label: lang.setbordervisible,
            cmdName: "setbordervisible"
          }
        ]
      },
      {
        group: lang.tablesort,
        icon: "tablesort",
        subMenu: [
          {
            label: lang.enablesort,
            cmdName: "enablesort"
          },
          {
            label: lang.disablesort,
            cmdName: "disablesort"
          },
          "-",
          {
            label: lang.reversecurrent,
            cmdName: "sorttable",
            value: "reversecurrent"
          },
          {
            label: lang.orderbyasc,
            cmdName: "sorttable",
            value: "orderbyasc"
          },
          {
            label: lang.reversebyasc,
            cmdName: "sorttable",
            value: "reversebyasc"
          },
          {
            label: lang.orderbynum,
            cmdName: "sorttable",
            value: "orderbynum"
          },
          {
            label: lang.reversebynum,
            cmdName: "sorttable",
            value: "reversebynum"
          }
        ]
      },
      {
        group: lang.borderbk,
        icon: "borderBack",
        subMenu: [
          {
            label: lang.setcolor,
            cmdName: "interlacetable",
            exec: function() {
              this.execCommand("interlacetable");
            }
          },
          {
            label: lang.unsetcolor,
            cmdName: "uninterlacetable",
            exec: function() {
              this.execCommand("uninterlacetable");
            }
          },
          {
            label: lang.setbackground,
            cmdName: "settablebackground",
            exec: function() {
              this.execCommand("settablebackground", {
                repeat: true,
                colorList: ["#bbb", "#ccc"]
              });
            }
          },
          {
            label: lang.unsetbackground,
            cmdName: "cleartablebackground",
            exec: function() {
              this.execCommand("cleartablebackground");
            }
          },
          {
            label: lang.redandblue,
            cmdName: "settablebackground",
            exec: function() {
              this.execCommand("settablebackground", {
                repeat: true,
                colorList: ["red", "blue"]
              });
            }
          },
          {
            label: lang.threecolorgradient,
            cmdName: "settablebackground",
            exec: function() {
              this.execCommand("settablebackground", {
                repeat: true,
                colorList: ["#aaa", "#bbb", "#ccc"]
              });
            }
          }
        ]
      },
      {
        group: lang.aligntd,
        icon: "aligntd",
        subMenu: [
          {
            cmdName: "cellalignment",
            value: { align: "left", vAlign: "top" }
          },
          {
            cmdName: "cellalignment",
            value: { align: "center", vAlign: "top" }
          },
          {
            cmdName: "cellalignment",
            value: { align: "right", vAlign: "top" }
          },
          {
            cmdName: "cellalignment",
            value: { align: "left", vAlign: "middle" }
          },
          {
            cmdName: "cellalignment",
            value: { align: "center", vAlign: "middle" }
          },
          {
            cmdName: "cellalignment",
            value: { align: "right", vAlign: "middle" }
          },
          {
            cmdName: "cellalignment",
            value: { align: "left", vAlign: "bottom" }
          },
          {
            cmdName: "cellalignment",
            value: { align: "center", vAlign: "bottom" }
          },
          {
            cmdName: "cellalignment",
            value: { align: "right", vAlign: "bottom" }
          }
        ]
      },
      {
        group: lang.aligntable,
        icon: "aligntable",
        subMenu: [
          {
            cmdName: "tablealignment",
            className: "left",
            label: lang.tableleft,
            value: "left"
          },
          {
            cmdName: "tablealignment",
            className: "center",
            label: lang.tablecenter,
            value: "center"
          },
          {
            cmdName: "tablealignment",
            className: "right",
            label: lang.tableright,
            value: "right"
          }
        ]
      },
      "-",
      {
        label: lang.insertparagraphbefore,
        cmdName: "insertparagraph",
        value: true
      },
      {
        label: lang.insertparagraphafter,
        cmdName: "insertparagraph"
      },
      {
        label: lang["copy"],
        cmdName: "copy"
      },
      {
        label: lang["paste"],
        cmdName: "paste"
      }
    ];
  if (!items.length) {
    return;
  }
  var uiUtils = UE.ui.uiUtils;

  me.addListener("contextmenu", function(type, evt) {
    var offset = uiUtils.getViewportOffsetByEvent(evt);
    me.fireEvent("beforeselectionchange");
    if (menu) {
      menu.destroy();
    }
    for (var i = 0, ti, contextItems = []; (ti = items[i]); i++) {
      var last;
      (function(item) {
        if (item == "-") {
          if ((last = contextItems[contextItems.length - 1]) && last !== "-") {
            contextItems.push("-");
          }
        } else if (item.hasOwnProperty("group")) {
          for (var j = 0, cj, subMenu = []; (cj = item.subMenu[j]); j++) {
            (function(subItem) {
              if (subItem == "-") {
                if ((last = subMenu[subMenu.length - 1]) && last !== "-") {
                  subMenu.push("-");
                } else {
                  subMenu.splice(subMenu.length - 1);
                }
              } else {
                if (
                  (me.commands[subItem.cmdName] ||
                    UE.commands[subItem.cmdName] ||
                    subItem.query) &&
                  (subItem.query
                    ? subItem.query()
                    : me.queryCommandState(subItem.cmdName)) > -1
                ) {
                  subMenu.push({
                    label:
                      subItem.label ||
                        me.getLang(
                          "contextMenu." +
                            subItem.cmdName +
                            (subItem.value || "")
                        ) ||
                        "",
                    className:
                      "edui-for-" +
                        subItem.cmdName +
                        (subItem.className
                          ? " edui-for-" +
                              subItem.cmdName +
                              "-" +
                              subItem.className
                          : ""),
                    onclick: subItem.exec
                      ? function() {
                          subItem.exec.call(me);
                        }
                      : function() {
                          me.execCommand(subItem.cmdName, subItem.value);
                        }
                  });
                }
              }
            })(cj);
          }
          if (subMenu.length) {
            function getLabel() {
              switch (item.icon) {
                case "table":
                  return me.getLang("contextMenu.table");
                case "justifyjustify":
                  return me.getLang("contextMenu.paragraph");
                case "aligntd":
                  return me.getLang("contextMenu.aligntd");
                case "aligntable":
                  return me.getLang("contextMenu.aligntable");
                case "tablesort":
                  return lang.tablesort;
                case "borderBack":
                  return lang.borderbk;
                default:
                  return "";
              }
            }
            contextItems.push({
              //todo 修正成自动获取方式
              label: getLabel(),
              className: "edui-for-" + item.icon,
              subMenu: {
                items: subMenu,
                editor: me
              }
            });
          }
        } else {
          //有可能commmand没有加载右键不能出来，或者没有command也想能展示出来添加query方法
          if (
            (me.commands[item.cmdName] ||
              UE.commands[item.cmdName] ||
              item.query) &&
            (item.query
              ? item.query.call(me)
              : me.queryCommandState(item.cmdName)) > -1
          ) {
            contextItems.push({
              label: item.label || me.getLang("contextMenu." + item.cmdName),
              className:
                "edui-for-" +
                  (item.icon ? item.icon : item.cmdName + (item.value || "")),
              onclick: item.exec
                ? function() {
                    item.exec.call(me);
                  }
                : function() {
                    me.execCommand(item.cmdName, item.value);
                  }
            });
          }
        }
      })(ti);
    }
    if (contextItems[contextItems.length - 1] == "-") {
      contextItems.pop();
    }

    menu = new UE.ui.Menu({
      items: contextItems,
      className: "edui-contextmenu",
      editor: me
    });
    menu.render();
    menu.showAt(offset);

    me.fireEvent("aftershowcontextmenu", menu);

    domUtils.preventDefault(evt);
    if (browser.ie) {
      var ieRange;
      try {
        ieRange = me.selection.getNative().createRange();
      } catch (e) {
        return;
      }
      if (ieRange.item) {
        var range = new dom.Range(me.document);
        range.selectNode(ieRange.item(0)).select(true, true);
      }
    }
  });

  // 添加复制的flash按钮
  me.addListener("aftershowcontextmenu", function(type, menu) {
    if (me.zeroclipboard) {
      var items = menu.items;
      for (var key in items) {
        if (items[key].className == "edui-for-copy") {
          me.zeroclipboard.clip(items[key].getDom());
        }
      }
    }
  });
};


// plugins/shortcutmenu.js
///import core
///commands       弹出菜单
// commandsName  popupmenu
///commandsTitle  弹出菜单
/**
 * 弹出菜单
 * @function
 * @name baidu.editor.plugins.popupmenu
 * @author xuheng
 */

UE.plugins["shortcutmenu"] = function() {
  var me = this,
    menu,
    items = me.options.shortcutMenu || [];

  if (!items.length) {
    return;
  }

  me.addListener("contextmenu mouseup", function(type, e) {
    var me = this,
      customEvt = {
        type: type,
        target: e.target || e.srcElement,
        screenX: e.screenX,
        screenY: e.screenY,
        clientX: e.clientX,
        clientY: e.clientY
      };

    setTimeout(function() {
      var rng = me.selection.getRange();
      if (rng.collapsed === false || type == "contextmenu") {
        // 未选中文字情况下不显示
        if(!me.selection.getText()){
          return
        }
        if (!menu) {
          menu = new baidu.editor.ui.ShortCutMenu({
            editor: me,
            items: items,
            theme: me.options.theme,
            className: "edui-shortcutmenu"
          });

          menu.render();
          me.fireEvent("afterrendershortcutmenu", menu);
        }

        menu.show(customEvt, !!UE.plugins["contextmenu"]);
      }
    });

    if (type == "contextmenu") {
      domUtils.preventDefault(e);
      if (browser.ie9below) {
        var ieRange;
        try {
          ieRange = me.selection.getNative().createRange();
        } catch (e) {
          return;
        }
        if (ieRange.item) {
          var range = new dom.Range(me.document);
          range.selectNode(ieRange.item(0)).select(true, true);
        }
      }
    }
  });

  me.addListener("keydown", function(type) {
    if (type == "keydown") {
      menu && !menu.isHidden && menu.hide();
    }
  });
};


// plugins/basestyle.js
/**
 * B、I、sub、super命令支持
 * @file
 * @since 1.2.6.1
 */

UE.plugins["basestyle"] = function() {
  /**
     * 字体加粗
     * @command bold
     * @param { String } cmd 命令字符串
     * @remind 对已加粗的文本内容执行该命令， 将取消加粗
     * @method execCommand
     * @example
     * ```javascript
     * //editor是编辑器实例
     * //对当前选中的文本内容执行加粗操作
     * //第一次执行， 文本内容加粗
     * editor.execCommand( 'bold' );
     *
     * //第二次执行， 文本内容取消加粗
     * editor.execCommand( 'bold' );
     * ```
     */

  /**
     * 字体倾斜
     * @command italic
     * @method execCommand
     * @param { String } cmd 命令字符串
     * @remind 对已倾斜的文本内容执行该命令， 将取消倾斜
     * @example
     * ```javascript
     * //editor是编辑器实例
     * //对当前选中的文本内容执行斜体操作
     * //第一次操作， 文本内容将变成斜体
     * editor.execCommand( 'italic' );
     *
     * //再次对同一文本内容执行， 则文本内容将恢复正常
     * editor.execCommand( 'italic' );
     * ```
     */

  /**
     * 下标文本，与“superscript”命令互斥
     * @command subscript
     * @method execCommand
     * @remind  把选中的文本内容切换成下标文本， 如果当前选中的文本已经是下标， 则该操作会把文本内容还原成正常文本
     * @param { String } cmd 命令字符串
     * @example
     * ```javascript
     * //editor是编辑器实例
     * //对当前选中的文本内容执行下标操作
     * //第一次操作， 文本内容将变成下标文本
     * editor.execCommand( 'subscript' );
     *
     * //再次对同一文本内容执行， 则文本内容将恢复正常
     * editor.execCommand( 'subscript' );
     * ```
     */

  /**
     * 上标文本，与“subscript”命令互斥
     * @command superscript
     * @method execCommand
     * @remind 把选中的文本内容切换成上标文本， 如果当前选中的文本已经是上标， 则该操作会把文本内容还原成正常文本
     * @param { String } cmd 命令字符串
     * @example
     * ```javascript
     * //editor是编辑器实例
     * //对当前选中的文本内容执行上标操作
     * //第一次操作， 文本内容将变成上标文本
     * editor.execCommand( 'superscript' );
     *
     * //再次对同一文本内容执行， 则文本内容将恢复正常
     * editor.execCommand( 'superscript' );
     * ```
     */
  var basestyles = {
    bold: ["strong", "b"],
    italic: ["em", "i"],
    subscript: ["sub"],
    superscript: ["sup"]
  },
    getObj = function(editor, tagNames) {
      return domUtils.filterNodeList(
        editor.selection.getStartElementPath(),
        tagNames
      );
    },
    me = this;
  //添加快捷键
  me.addshortcutkey({
    Bold: "ctrl+66", //^B
    Italic: "ctrl+73", //^I
    Underline: "ctrl+85" //^U
  });
  me.addInputRule(function(root) {
    utils.each(root.getNodesByTagName("b i"), function(node) {
      switch (node.tagName) {
        case "b":
          node.tagName = "strong";
          break;
        case "i":
          node.tagName = "em";
      }
    });
  });
  for (var style in basestyles) {
    (function(cmd, tagNames) {
      me.commands[cmd] = {
        execCommand: function(cmdName) {
          var range = me.selection.getRange(),
            obj = getObj(this, tagNames);
          if (range.collapsed) {
            if (obj) {
              var tmpText = me.document.createTextNode("");
              range.insertNode(tmpText).removeInlineStyle(tagNames);
              range.setStartBefore(tmpText);
              domUtils.remove(tmpText);
            } else {
              var tmpNode = range.document.createElement(tagNames[0]);
              if (cmdName == "superscript" || cmdName == "subscript") {
                tmpText = me.document.createTextNode("");
                range
                  .insertNode(tmpText)
                  .removeInlineStyle(["sub", "sup"])
                  .setStartBefore(tmpText)
                  .collapse(true);
              }
              range.insertNode(tmpNode).setStart(tmpNode, 0);
            }
            range.collapse(true);
          } else {
            if (cmdName == "superscript" || cmdName == "subscript") {
              if (!obj || obj.tagName.toLowerCase() != cmdName) {
                range.removeInlineStyle(["sub", "sup"]);
              }
            }
            obj
              ? range.removeInlineStyle(tagNames)
              : range.applyInlineStyle(tagNames[0]);
          }
          range.select();
        },
        queryCommandState: function() {
          return getObj(this, tagNames) ? 1 : 0;
        }
      };
    })(style, basestyles[style]);
  }
};


// plugins/elementpath.js
/**
 * 选取路径命令
 * @file
 */
UE.plugins["elementpath"] = function() {
  var currentLevel,
    tagNames,
    me = this;
  me.setOpt("elementPathEnabled", true);
  if (!me.options.elementPathEnabled) {
    return;
  }
  me.commands["elementpath"] = {
    execCommand: function(cmdName, level) {
      var start = tagNames[level],
        range = me.selection.getRange();
      currentLevel = level * 1;
      range.selectNode(start).select();
    },
    queryCommandValue: function() {
      //产生一个副本，不能修改原来的startElementPath;
      var parents = [].concat(this.selection.getStartElementPath()).reverse(),
        names = [];
      tagNames = parents;
      for (var i = 0, ci; (ci = parents[i]); i++) {
        if (ci.nodeType == 3) {
          continue;
        }
        var name = ci.tagName.toLowerCase();
        if (name == "img" && ci.getAttribute("anchorname")) {
          name = "anchor";
        }
        names[i] = name;
        if (currentLevel == i) {
          currentLevel = -1;
          break;
        }
      }
      return names;
    }
  };
};


// plugins/formatmatch.js
/**
 * 格式刷，只格式inline的
 * @file
 * @since 1.2.6.1
 */

/**
 * 格式刷
 * @command formatmatch
 * @method execCommand
 * @remind 该操作不能复制段落格式
 * @param { String } cmd 命令字符串
 * @example
 * ```javascript
 * //editor是编辑器实例
 * //获取格式刷
 * editor.execCommand( 'formatmatch' );
 * ```
 */
UE.plugins["formatmatch"] = function() {
  var me = this,
    list = [],
    img,
    flag = 0;

  me.addListener("reset", function() {
    list = [];
    flag = 0;
  });

  function addList(type, evt) {
    if (browser.webkit) {
      var target = evt.target.tagName == "IMG" ? evt.target : null;
    }

    function addFormat(range) {
      if (text) {
        range.selectNode(text);
      }
      return range.applyInlineStyle(list[list.length - 1].tagName, null, list);
    }

    me.undoManger && me.undoManger.save();

    var range = me.selection.getRange(),
      imgT = target || range.getClosedNode();
    if (img && imgT && imgT.tagName == "IMG") {
      //trace:964

      imgT.style.cssText +=
        ";float:" +
        (img.style.cssFloat || img.style.styleFloat || "none") +
        ";display:" +
        (img.style.display || "inline");

      img = null;
    } else {
      if (!img) {
        var collapsed = range.collapsed;
        if (collapsed) {
          var text = me.document.createTextNode("match");
          range.insertNode(text).select();
        }
        me.__hasEnterExecCommand = true;
        //不能把block上的属性干掉
        //trace:1553
        var removeFormatAttributes = me.options.removeFormatAttributes;
        me.options.removeFormatAttributes = "";
        me.execCommand("removeformat");
        me.options.removeFormatAttributes = removeFormatAttributes;
        me.__hasEnterExecCommand = false;
        //trace:969
        range = me.selection.getRange();
        if (list.length) {
          addFormat(range);
        }
        if (text) {
          range.setStartBefore(text).collapse(true);
        }
        range.select();
        text && domUtils.remove(text);
      }
    }

    me.undoManger && me.undoManger.save();
    me.removeListener("mouseup", addList);
    flag = 0;
  }

  me.commands["formatmatch"] = {
    execCommand: function(cmdName) {
      if (flag) {
        flag = 0;
        list = [];
        me.removeListener("mouseup", addList);
        return;
      }

      var range = me.selection.getRange();
      img = range.getClosedNode();
      if (!img || img.tagName != "IMG") {
        range.collapse(true).shrinkBoundary();
        var start = range.startContainer;
        list = domUtils.findParents(start, true, function(node) {
          return !domUtils.isBlockElm(node) && node.nodeType == 1;
        });
        //a不能加入格式刷, 并且克隆节点
        for (var i = 0, ci; (ci = list[i]); i++) {
          if (ci.tagName == "A") {
            list.splice(i, 1);
            break;
          }
        }
      }

      me.addListener("mouseup", addList);
      flag = 1;
    },
    queryCommandState: function() {
      return flag;
    },
    notNeedUndo: 1
  };
};


// plugins/searchreplace.js
///import core
///commands 查找替换
///commandsName  SearchReplace
///commandsTitle  查询替换
///commandsDialog  dialogs\searchreplace
/**
 * @description 查找替换
 * @author zhanyi
 */

UE.plugin.register("searchreplace", function() {
  var me = this;

  var _blockElm = { table: 1, tbody: 1, tr: 1, ol: 1, ul: 1 };

  var lastRng = null;

  function getText(node) {
    var text = node.nodeType == 3
      ? node.nodeValue
      : node[browser.ie ? "innerText" : "textContent"];
    return text.replace(domUtils.fillChar, "");
  }

  function findTextInString(textContent, opt, currentIndex) {
    var str = opt.searchStr;

    var reg = new RegExp(str, "g" + (opt.casesensitive ? "" : "i")),
      match;

    if (opt.dir == -1) {
      textContent = textContent.substr(0, currentIndex);
      textContent = textContent.split("").reverse().join("");
      str = str.split("").reverse().join("");
      match = reg.exec(textContent);
      if (match) {
        return currentIndex - match.index - str.length;
      }
    } else {
      textContent = textContent.substr(currentIndex);
      match = reg.exec(textContent);
      if (match) {
        return match.index + currentIndex;
      }
    }

    return -1;
  }
  function findTextBlockElm(node, currentIndex, opt) {
    var textContent,
      index,
      methodName = opt.all || opt.dir == 1 ? "getNextDomNode" : "getPreDomNode";
    if (domUtils.isBody(node)) {
      node = node.firstChild;
    }
    var first = 1;
    while (node) {
      textContent = getText(node);
      index = findTextInString(textContent, opt, currentIndex);
      first = 0;
      if (index != -1) {
        return {
          node: node,
          index: index
        };
      }
      node = domUtils[methodName](node);
      while (node && _blockElm[node.nodeName.toLowerCase()]) {
        node = domUtils[methodName](node, true);
      }
      if (node) {
        currentIndex = opt.dir == -1 ? getText(node).length : 0;
      }
    }
  }
  function findNTextInBlockElm(node, index, str) {
    var currentIndex = 0,
      currentNode = node.firstChild,
      currentNodeLength = 0,
      result;
    while (currentNode) {
      if (currentNode.nodeType == 3) {
        currentNodeLength = getText(currentNode).replace(
          /(^[\t\r\n]+)|([\t\r\n]+$)/,
          ""
        ).length;
        currentIndex += currentNodeLength;
        if (currentIndex >= index) {
          return {
            node: currentNode,
            index: currentNodeLength - (currentIndex - index)
          };
        }
      } else if (!dtd.$empty[currentNode.tagName]) {
        currentNodeLength = getText(currentNode).replace(
          /(^[\t\r\n]+)|([\t\r\n]+$)/,
          ""
        ).length;
        currentIndex += currentNodeLength;
        if (currentIndex >= index) {
          result = findNTextInBlockElm(
            currentNode,
            currentNodeLength - (currentIndex - index),
            str
          );
          if (result) {
            return result;
          }
        }
      }
      currentNode = domUtils.getNextDomNode(currentNode);
    }
  }

  function searchReplace(me, opt) {
    var rng = lastRng || me.selection.getRange(),
      startBlockNode,
      searchStr = opt.searchStr,
      span = me.document.createElement("span");
    span.innerHTML = "$$ueditor_searchreplace_key$$";

    rng.shrinkBoundary(true);

    //判断是不是第一次选中
    if (!rng.collapsed) {
      rng.select();
      var rngText = me.selection.getText();
      if (
        new RegExp(
          "^" + opt.searchStr + "$",
          opt.casesensitive ? "" : "i"
        ).test(rngText)
      ) {
        if (opt.replaceStr != undefined) {
          replaceText(rng, opt.replaceStr);
          rng.select();
          return true;
        } else {
          rng.collapse(opt.dir == -1);
        }
      }
    }

    rng.insertNode(span);
    rng.enlargeToBlockElm(true);
    startBlockNode = rng.startContainer;
    var currentIndex = getText(startBlockNode).indexOf(
      "$$ueditor_searchreplace_key$$"
    );
    rng.setStartBefore(span);
    domUtils.remove(span);
    var result = findTextBlockElm(startBlockNode, currentIndex, opt);
    if (result) {
      var rngStart = findNTextInBlockElm(result.node, result.index, searchStr);
      var rngEnd = findNTextInBlockElm(
        result.node,
        result.index + searchStr.length,
        searchStr
      );
      rng
        .setStart(rngStart.node, rngStart.index)
        .setEnd(rngEnd.node, rngEnd.index);

      if (opt.replaceStr !== undefined) {
        replaceText(rng, opt.replaceStr);
      }
      rng.select();
      return true;
    } else {
      rng.setCursor();
    }
  }
  function replaceText(rng, str) {
    str = me.document.createTextNode(str);
    rng.deleteContents().insertNode(str);
  }
  return {
    commands: {
      searchreplace: {
        execCommand: function(cmdName, opt) {
          utils.extend(
            opt,
            {
              all: false,
              casesensitive: false,
              dir: 1
            },
            true
          );
          var num = 0;
          if (opt.all) {
            lastRng = null;
            var rng = me.selection.getRange(),
              first = me.body.firstChild;
            if (first && first.nodeType == 1) {
              rng.setStart(first, 0);
              rng.shrinkBoundary(true);
            } else if (first.nodeType == 3) {
              rng.setStartBefore(first);
            }
            rng.collapse(true).select(true);
            if (opt.replaceStr !== undefined) {
              me.fireEvent("saveScene");
            }
            while (searchReplace(this, opt)) {
              num++;
              lastRng = me.selection.getRange();
              lastRng.collapse(opt.dir == -1);
            }
            if (num) {
              me.fireEvent("saveScene");
            }
          } else {
            if (opt.replaceStr !== undefined) {
              me.fireEvent("saveScene");
            }
            if (searchReplace(this, opt)) {
              num++;
              lastRng = me.selection.getRange();
              lastRng.collapse(opt.dir == -1);
            }
            if (num) {
              me.fireEvent("saveScene");
            }
          }

          return num;
        },
        notNeedUndo: 1
      }
    },
    bindEvents: {
      clearlastSearchResult: function() {
        lastRng = null;
      }
    }
  };
});


// plugins/customstyle.js
/**
 * 自定义样式
 * @file
 * @since 1.2.6.1
 */

/**
 * 根据config配置文件里“customstyle”选项的值对匹配的标签执行样式替换。
 * @command customstyle
 * @method execCommand
 * @param { String } cmd 命令字符串
 * @example
 * ```javascript
 * editor.execCommand( 'customstyle' );
 * ```
 */
UE.plugins["customstyle"] = function() {
  var me = this;
  me.setOpt({
    customstyle: [
      {
        tag: "h1",
        name: "tc",
        style:
          "font-size:32px;line-height:40px;font-weight:bold;border-bottom:#ccc 2px solid;padding:0 4px 0 0;text-align:center;"
      },
      {
        tag: "h1",
        name: "tl",
        style:
          "font-size:32px;line-height:40px;font-weight:bold;border-bottom:#ccc 2px solid;padding:0 4px 0 0;text-align:left;"
      },
      {
        tag: "span",
        name: "im",
        style:
          "font-size:16px;font-style:italic;font-weight:bold;line-height:18px;"
      },
      {
        tag: "span",
        name: "hi",
        style:
          "font-size:16px;font-style:italic;font-weight:bold;color:rgb(51, 153, 204);line-height:18px;"
      }
    ]
  });
  me.commands["customstyle"] = {
    execCommand: function(cmdName, obj) {
      var me = this,
        tagName = obj.tag,
        node = domUtils.findParent(
          me.selection.getStart(),
          function(node) {
            return node.getAttribute("label");
          },
          true
        ),
        range,
        bk,
        tmpObj = {};
      for (var p in obj) {
        if (obj[p] !== undefined) tmpObj[p] = obj[p];
      }
      delete tmpObj.tag;
      if (node && node.getAttribute("label") == obj.label) {
        range = this.selection.getRange();
        bk = range.createBookmark();
        if (range.collapsed) {
          //trace:1732 删掉自定义标签，要有p来回填站位
          if (dtd.$block[node.tagName]) {
            var fillNode = me.document.createElement("p");
            domUtils.moveChild(node, fillNode);
            node.parentNode.insertBefore(fillNode, node);
            domUtils.remove(node);
          } else {
            domUtils.remove(node, true);
          }
        } else {
          var common = domUtils.getCommonAncestor(bk.start, bk.end),
            nodes = domUtils.getElementsByTagName(common, tagName);
          if (new RegExp(tagName, "i").test(common.tagName)) {
            nodes.push(common);
          }
          for (var i = 0, ni; (ni = nodes[i++]); ) {
            if (ni.getAttribute("label") == obj.label) {
              var ps = domUtils.getPosition(ni, bk.start),
                pe = domUtils.getPosition(ni, bk.end);
              if (
                (ps & domUtils.POSITION_FOLLOWING ||
                  ps & domUtils.POSITION_CONTAINS) &&
                (pe & domUtils.POSITION_PRECEDING ||
                  pe & domUtils.POSITION_CONTAINS)
              )
                if (dtd.$block[tagName]) {
                  var fillNode = me.document.createElement("p");
                  domUtils.moveChild(ni, fillNode);
                  ni.parentNode.insertBefore(fillNode, ni);
                }
              domUtils.remove(ni, true);
            }
          }
          node = domUtils.findParent(
            common,
            function(node) {
              return node.getAttribute("label") == obj.label;
            },
            true
          );
          if (node) {
            domUtils.remove(node, true);
          }
        }
        range.moveToBookmark(bk).select();
      } else {
        if (dtd.$block[tagName]) {
          this.execCommand("paragraph", tagName, tmpObj, "customstyle");
          range = me.selection.getRange();
          if (!range.collapsed) {
            range.collapse();
            node = domUtils.findParent(
              me.selection.getStart(),
              function(node) {
                return node.getAttribute("label") == obj.label;
              },
              true
            );
            var pNode = me.document.createElement("p");
            domUtils.insertAfter(node, pNode);
            domUtils.fillNode(me.document, pNode);
            range.setStart(pNode, 0).setCursor();
          }
        } else {
          range = me.selection.getRange();
          if (range.collapsed) {
            node = me.document.createElement(tagName);
            domUtils.setAttributes(node, tmpObj);
            range.insertNode(node).setStart(node, 0).setCursor();

            return;
          }

          bk = range.createBookmark();
          range.applyInlineStyle(tagName, tmpObj).moveToBookmark(bk).select();
        }
      }
    },
    queryCommandValue: function() {
      var parent = domUtils.filterNodeList(
        this.selection.getStartElementPath(),
        function(node) {
          return node.getAttribute("label");
        }
      );
      return parent ? parent.getAttribute("label") : "";
    }
  };
  //当去掉customstyle是，如果是块元素，用p代替
  me.addListener("keyup", function(type, evt) {
    var keyCode = evt.keyCode || evt.which;

    if (keyCode == 32 || keyCode == 13) {
      var range = me.selection.getRange();
      if (range.collapsed) {
        var node = domUtils.findParent(
          me.selection.getStart(),
          function(node) {
            return node.getAttribute("label");
          },
          true
        );
        if (node && dtd.$block[node.tagName] && domUtils.isEmptyNode(node)) {
          var p = me.document.createElement("p");
          domUtils.insertAfter(node, p);
          domUtils.fillNode(me.document, p);
          domUtils.remove(node);
          range.setStart(p, 0).setCursor();
        }
      }
    }
  });
};


// plugins/catchremoteimage.js
///import core
///commands 远程图片抓取
///commandsName  catchRemoteImage,catchremoteimageenable
///commandsTitle  远程图片抓取
/**
 * 远程图片抓取,当开启本插件时所有不符合本地域名的图片都将被抓取成为本地服务器上的图片
 */
UE.plugins["catchremoteimage"] = function () {
  var me = this,
    ajax = UE.ajax;

  /* 设置默认值 */
  if (me.options.catchRemoteImageEnable === false) {
    return;
  }
  me.setOpt({
    catchRemoteImageEnable: false
  });

  var catcherLocalDomain = me.getOpt("catcherLocalDomain"),
    catcherActionUrl = me.getActionUrl(me.getOpt("catcherActionName")),
    catcherUrlPrefix = me.getOpt("catcherUrlPrefix"),
    catcherFieldName = me.getOpt("catcherFieldName");

  me.addListener('serverConfigLoaded', function () {
    catcherLocalDomain = me.getOpt("catcherLocalDomain");
    catcherActionUrl = me.getActionUrl(me.getOpt("catcherActionName"));
    catcherUrlPrefix = me.getOpt("catcherUrlPrefix");
    catcherFieldName = me.getOpt("catcherFieldName");
  });

  me.addListener("afterpaste", function () {
    me.fireEvent("catchremoteimage");
  });

  var catchRemoteImageCatching = false;

  function sendApi(imgs, callbacks) {
    var params = utils.serializeParam(me.queryCommandValue("serverparam")) || "",
      url = utils.formatUrl(
        catcherActionUrl +
        (catcherActionUrl.indexOf("?") === -1 ? "?" : "&") +
        params
      ),
      isJsonp = utils.isCrossDomainUrl(url),
      opt = {
        method: "POST",
        dataType: isJsonp ? "jsonp" : "",
        timeout: 60000, //单位：毫秒，回调请求超时设置。目标用户如果网速不是很快的话此处建议设置一个较大的数值
        headers: me.options.serverHeaders || {},
        onsuccess: callbacks["success"],
        onerror: callbacks["error"]
      };
    opt[catcherFieldName] = imgs;
    ajax.request(url, opt);
  }

  function catchElement(type, ele, imageUrl) {
    sendApi([imageUrl], {
      //成功抓取
      success: function (r) {
        try {
          var info = r.state !== undefined
            ? r
            : eval("(" + r.responseText + ")");
        } catch (e) {
          return;
        }

        /* 获取源路径和新路径 */
        var oldSrc,
          newSrc,
          oldBgIMG,
          newBgIMG,
          list = info.list;
        var catchFailList = [];
        var catchSuccessList = [];
        var failIMG = me.options.themePath + me.options.theme + '/images/img-cracked.png';
        var loadingIMG = me.options.themePath + me.options.theme + '/images/spacer.gif';

        var cj = list[0];
        switch (type) {
          case 'image':
            oldSrc = ele.getAttribute("_src") || ele.src || "";
            if (cj.state === "SUCCESS") {
              newSrc = catcherUrlPrefix + cj.url;
              // 上传成功是删除uploading动画
              domUtils.removeClasses(ele, "loadingclass");
              domUtils.setAttributes(ele, {
                "src": newSrc,
                "_src": newSrc,
                "data-catch-result": "success"
              });
              catchSuccessList.push(ele);
            } else {
              // 替换成统一的失败图片
              domUtils.removeClasses(ele, "loadingclass");
              domUtils.setAttributes(ele, {
                "src": failIMG,
                "_src": failIMG,
                "data-catch-result": "fail" // 添加catch失败标记
              });
              catchFailList.push(ele);
            }
            break;
          case 'background':
            oldBgIMG = ele.getAttribute("data-background") || "";
            if (cj.state === "SUCCESS") {
              newBgIMG = catcherUrlPrefix + cj.url;
              ele.style.cssText = ele.style.cssText.replace(loadingIMG, newBgIMG);
              domUtils.removeAttributes(ele, "data-background");
              domUtils.setAttributes(ele, {
                "data-catch-result": "success"   // 添加catch成功标记
              });
              catchSuccessList.push(ele);
            } else {
              ele.style.cssText = ele.style.cssText.replace(loadingIMG, failIMG);
              domUtils.removeAttributes(ele, "data-background");
              domUtils.setAttributes(ele, {
                "data-catch-result": "fail"   // 添加catch失败标记
              });
              catchFailList.push(ele);
            }
            break;
        }
        // 监听事件添加成功抓取和抓取失败的dom列表参数
        me.fireEvent('catchremotesuccess', catchSuccessList, catchFailList);
        catchRemoteImageCatching = false;
        setTimeout(function () {
          me.fireEvent('catchremoteimage');
        }, 0);
      },
      //回调失败，本次请求超时
      error: function () {
        me.fireEvent('catchremoteerror');
        catchRemoteImageCatching = false;
        setTimeout(function () {
          me.fireEvent('catchremoteimage');
        }, 0);
      }
    });
  }

  function catchRemoteImage() {
    if (catchRemoteImageCatching) {
      return;
    }
    catchRemoteImageCatching = true;

    var loadingIMG = me.options.themePath + me.options.theme + '/images/spacer.gif',
      imgs = me.document.querySelectorAll('[style*="url"],img'),
      test = function (src, urls) {
        if (src.indexOf(location.host) !== -1 || /(^\.)|(^\/)/.test(src)) {
          return true;
        }
        if (urls) {
          for (var j = 0, url; (url = urls[j++]);) {
            if (src.indexOf(url) !== -1) {
              return true;
            }
          }
        }
        return false;
      };

    for (var i = 0, ci; (ci = imgs[i++]);) {
      if (ci.getAttribute("data-word-image") || ci.getAttribute('data-catch-result')) {
        continue;
      }
      if (ci.nodeName === "IMG") {
        var src = ci.getAttribute("_src") || ci.src || "";
        if (/^(https?|ftp):/i.test(src) && !test(src, catcherLocalDomain)) {
          catchElement('image', ci, src);
          domUtils.setAttributes(ci, {
            class: "loadingclass",
            _src: src,
            src: loadingIMG
          })
          return;
        }
      } else {
        var backgroundImageurl = ci.style.cssText.replace(/.*\s?url\([\'\"]?/, '').replace(/[\'\"]?\).*/, '');
        if (/^(https?|ftp):/i.test(backgroundImageurl) && !test(backgroundImageurl, catcherLocalDomain)) {
          catchElement('background', ci, backgroundImageurl);
          ci.style.cssText = ci.style.cssText.replace(backgroundImageurl, loadingIMG);
          domUtils.setAttributes(ci, {
            "data-background": backgroundImageurl
          })
          return;
        }
      }
    }

  };

  me.addListener("catchremoteimage", function () {
    catchRemoteImage();
  });
};


// plugins/insertparagraph.js
/**
 * 插入段落
 * @file
 * @since 1.2.6.1
 */

/**
 * 插入段落
 * @command insertparagraph
 * @method execCommand
 * @param { String } cmd 命令字符串
 * @example
 * ```javascript
 * //editor是编辑器实例
 * editor.execCommand( 'insertparagraph' );
 * ```
 */

UE.commands["insertparagraph"] = {
  execCommand: function(cmdName, front) {
    var me = this,
      range = me.selection.getRange(),
      start = range.startContainer,
      tmpNode;
    while (start) {
      if (domUtils.isBody(start)) {
        break;
      }
      tmpNode = start;
      start = start.parentNode;
    }
    if (tmpNode) {
      var p = me.document.createElement("p");
      if (front) {
        tmpNode.parentNode.insertBefore(p, tmpNode);
      } else {
        tmpNode.parentNode.insertBefore(p, tmpNode.nextSibling);
      }
      domUtils.fillNode(me.document, p);
      range.setStart(p, 0).setCursor(false, true);
    }
  }
};


// plugins/template.js
///import core
///import plugins\inserthtml.js
///import plugins\cleardoc.js
///commands 模板
///commandsName  template
///commandsTitle  模板
///commandsDialog  dialogs\template
UE.plugins["template"] = function() {
  UE.commands["template"] = {
    execCommand: function(cmd, obj) {
      obj.html && this.execCommand("inserthtml", obj.html);
    }
  };
  this.addListener("click", function(type, evt) {
    var el = evt.target || evt.srcElement,
      range = this.selection.getRange();
    var tnode = domUtils.findParent(
      el,
      function(node) {
        if (node.className && domUtils.hasClass(node, "ue_t")) {
          return node;
        }
      },
      true
    );
    tnode && range.selectNode(tnode).shrinkBoundary().select();
  });
  this.addListener("keydown", function(type, evt) {
    var range = this.selection.getRange();
    if (!range.collapsed) {
      if (!evt.ctrlKey && !evt.metaKey && !evt.shiftKey && !evt.altKey) {
        var tnode = domUtils.findParent(
          range.startContainer,
          function(node) {
            if (node.className && domUtils.hasClass(node, "ue_t")) {
              return node;
            }
          },
          true
        );
        if (tnode) {
          domUtils.removeClasses(tnode, ["ue_t"]);
        }
      }
    }
  });
};


// plugins/autoupload.js
/**
 * @description
 * 1.拖放文件到编辑区域，自动上传并插入到选区
 * 2.插入粘贴板的图片，自动上传并插入到选区
 * @author Jinqn
 * @date 2013-10-14
 */
UE.plugin.register("autoupload", function() {
  function sendAndInsertFile(file, editor) {
    var me = editor;
    //模拟数据
    var fieldName,
      urlPrefix,
      maxSize,
      allowFiles,
      actionUrl,
      loadingHtml,
      errorHandler,
      successHandler,
      filetype = /image\/\w+/i.test(file.type) ? "image" : "file",
      loadingId = "loading_" + (+new Date()).toString(36);

    fieldName = me.getOpt(filetype + "FieldName");
    urlPrefix = me.getOpt(filetype + "UrlPrefix");
    maxSize = me.getOpt(filetype + "MaxSize");
    allowFiles = me.getOpt(filetype + "AllowFiles");
    actionUrl = me.getActionUrl(me.getOpt(filetype + "ActionName"));
    errorHandler = function(title) {
      var loader = me.document.getElementById(loadingId);
      loader && domUtils.remove(loader);
      me.fireEvent("showmessage", {
        id: loadingId,
        content: title,
        type: "error",
        timeout: 4000
      });
    };

    if (filetype == "image") {
      loadingHtml =
        '<img class="loadingclass" id="' +
        loadingId +
        '" src="' +
        me.options.themePath +
        me.options.theme +
        '/images/spacer.gif">';
      successHandler = function(data) {
        var link = urlPrefix + data.url,
          loader = me.document.getElementById(loadingId);
        if (loader) {
          domUtils.removeClasses(loader, "loadingclass");
          loader.setAttribute("src", link);
          loader.setAttribute("_src", link);
          loader.setAttribute("alt", data.original || "");
          loader.removeAttribute("id");
          me.trigger("contentchange", loader);
        }
      };
    } else {
      loadingHtml =
        "<p>" +
        '<img class="loadingclass" id="' +
        loadingId +
        '" src="' +
        me.options.themePath +
        me.options.theme +
        '/images/spacer.gif">' +
        "</p>";
      successHandler = function(data) {
        var link = urlPrefix + data.url,
          loader = me.document.getElementById(loadingId);

        var rng = me.selection.getRange(),
          bk = rng.createBookmark();
        rng.selectNode(loader).select();
        me.execCommand("insertfile", { url: link });
        rng.moveToBookmark(bk).select();
      };
    }

    /* 插入loading的占位符 */
    me.execCommand("inserthtml", loadingHtml);
    /* 判断后端配置是否没有加载成功 */
    if (!me.getOpt(filetype + "ActionName")) {
      errorHandler(me.getLang("autoupload.errorLoadConfig"));
      return;
    }
    /* 判断文件大小是否超出限制 */
    if (file.size > maxSize) {
      errorHandler(me.getLang("autoupload.exceedSizeError"));
      return;
    }
    /* 判断文件格式是否超出允许 */
    var fileext = file.name ? file.name.substr(file.name.lastIndexOf(".")) : "";
    if (
      (fileext && filetype != "image") ||
      (allowFiles &&
        (allowFiles.join("") + ".").indexOf(fileext.toLowerCase() + ".") == -1)
    ) {
      errorHandler(me.getLang("autoupload.exceedTypeError"));
      return;
    }

    /* 创建Ajax并提交 */
    var xhr = new XMLHttpRequest(),
      fd = new FormData(),
      params = utils.serializeParam(me.queryCommandValue("serverparam")) || "",
      url = utils.formatUrl(
        actionUrl + (actionUrl.indexOf("?") == -1 ? "?" : "&") + params
      );

    fd.append(
      fieldName,
      file,
      file.name || "blob." + file.type.substr("image/".length)
    );
    fd.append("type", "ajax");
    xhr.open("post", url, true);
    xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
    xhr.addEventListener("load", function(e) {
      try {
        var json = new Function("return " + utils.trim(e.target.response))();
        if (json.state == "SUCCESS" && json.url) {
          successHandler(json);
        } else {
          errorHandler(json.state);
        }
      } catch (er) {
        errorHandler(me.getLang("autoupload.loadError"));
      }
    });
    xhr.send(fd);
  }

  function getPasteImage(e) {
    return e.clipboardData &&
      e.clipboardData.items &&
      e.clipboardData.items.length == 1 &&
      /^image\//.test(e.clipboardData.items[0].type)
      ? e.clipboardData.items
      : null;
  }
  function getDropImage(e) {
    return e.dataTransfer && e.dataTransfer.files ? e.dataTransfer.files : null;
  }

  return {
    outputRule: function(root) {
      utils.each(root.getNodesByTagName("img"), function(n) {
        if (/\b(loaderrorclass)|(bloaderrorclass)\b/.test(n.getAttr("class"))) {
          n.parentNode.removeChild(n);
        }
      });
      utils.each(root.getNodesByTagName("p"), function(n) {
        if (/\bloadpara\b/.test(n.getAttr("class"))) {
          n.parentNode.removeChild(n);
        }
      });
    },
    bindEvents: {
      defaultOptions: {
        //默认间隔时间
        enableDragUpload: true,
        enablePasteUpload: true
      },
      //插入粘贴板的图片，拖放插入图片
      ready: function(e) {
        var me = this;
        if (window.FormData && window.FileReader) {
          var handler = function(e) {
            var hasImg = false,
              items;
            //获取粘贴板文件列表或者拖放文件列表
            items = e.type == "paste" ? getPasteImage(e) : getDropImage(e);
            if (items) {
              var len = items.length,
                file;
              while (len--) {
                file = items[len];
                if (file.getAsFile) file = file.getAsFile();
                if (file && file.size > 0) {
                  sendAndInsertFile(file, me);
                  hasImg = true;
                }
              }
              hasImg && e.preventDefault();
            }
          };

          if (me.getOpt("enablePasteUpload") !== false) {
            domUtils.on(me.body, "paste ", handler);
          }
          if (me.getOpt("enableDragUpload") !== false) {
            domUtils.on(me.body, "drop", handler);
            //取消拖放图片时出现的文字光标位置提示
            domUtils.on(me.body, "dragover", function(e) {
              if (e.dataTransfer.types[0] == "Files") {
                e.preventDefault();
              }
            });
          } else {
            if (browser.gecko) {
              domUtils.on(me.body, "drop", function(e) {
                if (getDropImage(e)) {
                  e.preventDefault();
                }
              });
            }
          }

          //设置loading的样式
          utils.cssRule(
            "loading",
            ".loadingclass{display:inline-block;cursor:default;background: url('" +
              this.options.themePath +
              this.options.theme +
              "/images/loading.gif') no-repeat center center transparent;border-radius:3px;outline:1px solid #EEE;margin-left:1px;height:22px;width:22px;}\n" +
              ".loaderrorclass{display:inline-block;cursor:default;background: url('" +
              this.options.themePath +
              this.options.theme +
              "/images/loaderror.png') no-repeat center center transparent;border-radius:3px;outline:1px solid #EEE;margin-right:1px;height:22px;width:22px;" +
              "}",
            this.document
          );
        }
      }
    }
  };
});


// plugins/section.js
/**
 * 目录大纲支持插件
 * @file
 * @since 1.3.0
 */
UE.plugin.register("section", function() {
  /* 目录节点对象 */
  function Section(option) {
    this.tag = "";
    (this.level = -1), (this.dom = null);
    this.nextSection = null;
    this.previousSection = null;
    this.parentSection = null;
    this.startAddress = [];
    this.endAddress = [];
    this.children = [];
  }
  function getSection(option) {
    var section = new Section();
    return utils.extend(section, option);
  }
  function getNodeFromAddress(startAddress, root) {
    var current = root;
    for (var i = 0; i < startAddress.length; i++) {
      if (!current.childNodes) return null;
      current = current.childNodes[startAddress[i]];
    }
    return current;
  }

  var me = this;

  return {
    bindMultiEvents: {
      type: "aftersetcontent afterscencerestore",
      handler: function() {
        me.fireEvent("updateSections");
      }
    },
    bindEvents: {
      /* 初始化、拖拽、粘贴、执行setcontent之后 */
      ready: function() {
        me.fireEvent("updateSections");
        domUtils.on(me.body, "drop paste", function() {
          me.fireEvent("updateSections");
        });
      },
      /* 执行paragraph命令之后 */
      afterexeccommand: function(type, cmd) {
        if (cmd == "paragraph") {
          me.fireEvent("updateSections");
        }
      },
      /* 部分键盘操作，触发updateSections事件 */
      keyup: function(type, e) {
        var me = this,
          range = me.selection.getRange();
        if (range.collapsed != true) {
          me.fireEvent("updateSections");
        } else {
          var keyCode = e.keyCode || e.which;
          if (keyCode == 13 || keyCode == 8 || keyCode == 46) {
            me.fireEvent("updateSections");
          }
        }
      }
    },
    commands: {
      getsections: {
        execCommand: function(cmd, levels) {
          var levelFn = levels || ["h1", "h2", "h3", "h4", "h5", "h6"];

          for (var i = 0; i < levelFn.length; i++) {
            if (typeof levelFn[i] == "string") {
              levelFn[i] = (function(fn) {
                return function(node) {
                  return node.tagName == fn.toUpperCase();
                };
              })(levelFn[i]);
            } else if (typeof levelFn[i] != "function") {
              levelFn[i] = function(node) {
                return null;
              };
            }
          }
          function getSectionLevel(node) {
            for (var i = 0; i < levelFn.length; i++) {
              if (levelFn[i](node)) return i;
            }
            return -1;
          }

          var me = this,
            Directory = getSection({ level: -1, title: "root" }),
            previous = Directory;

          function traversal(node, Directory) {
            var level,
              tmpSection = null,
              parent,
              child,
              children = node.childNodes;
            for (var i = 0, len = children.length; i < len; i++) {
              child = children[i];
              level = getSectionLevel(child);
              if (level >= 0) {
                var address = me.selection
                  .getRange()
                  .selectNode(child)
                  .createAddress(true).startAddress,
                  current = getSection({
                    tag: child.tagName,
                    title: child.innerText || child.textContent || "",
                    level: level,
                    dom: child,
                    startAddress: utils.clone(address, []),
                    endAddress: utils.clone(address, []),
                    children: []
                  });
                previous.nextSection = current;
                current.previousSection = previous;
                parent = previous;
                while (level <= parent.level) {
                  parent = parent.parentSection;
                }
                current.parentSection = parent;
                parent.children.push(current);
                tmpSection = previous = current;
              } else {
                child.nodeType === 1 && traversal(child, Directory);
                tmpSection &&
                  tmpSection.endAddress[tmpSection.endAddress.length - 1]++;
              }
            }
          }
          traversal(me.body, Directory);
          return Directory;
        },
        notNeedUndo: true
      },
      movesection: {
        execCommand: function(cmd, sourceSection, targetSection, isAfter) {
          var me = this,
            targetAddress,
            target;

          if (!sourceSection || !targetSection || targetSection.level == -1)
            return;

          targetAddress = isAfter
            ? targetSection.endAddress
            : targetSection.startAddress;
          target = getNodeFromAddress(targetAddress, me.body);

          /* 判断目标地址是否被源章节包含 */
          if (
            !targetAddress ||
            !target ||
            isContainsAddress(
              sourceSection.startAddress,
              sourceSection.endAddress,
              targetAddress
            )
          )
            return;

          var startNode = getNodeFromAddress(
            sourceSection.startAddress,
            me.body
          ),
            endNode = getNodeFromAddress(sourceSection.endAddress, me.body),
            current,
            nextNode;

          if (isAfter) {
            current = endNode;
            while (
              current &&
              !(
                domUtils.getPosition(startNode, current) &
                domUtils.POSITION_FOLLOWING
              )
            ) {
              nextNode = current.previousSibling;
              domUtils.insertAfter(target, current);
              if (current == startNode) break;
              current = nextNode;
            }
          } else {
            current = startNode;
            while (
              current &&
              !(
                domUtils.getPosition(current, endNode) &
                domUtils.POSITION_FOLLOWING
              )
            ) {
              nextNode = current.nextSibling;
              target.parentNode.insertBefore(current, target);
              if (current == endNode) break;
              current = nextNode;
            }
          }

          me.fireEvent("updateSections");

          /* 获取地址的包含关系 */
          function isContainsAddress(startAddress, endAddress, addressTarget) {
            var isAfterStartAddress = false,
              isBeforeEndAddress = false;
            for (var i = 0; i < startAddress.length; i++) {
              if (i >= addressTarget.length) break;
              if (addressTarget[i] > startAddress[i]) {
                isAfterStartAddress = true;
                break;
              } else if (addressTarget[i] < startAddress[i]) {
                break;
              }
            }
            for (var i = 0; i < endAddress.length; i++) {
              if (i >= addressTarget.length) break;
              if (addressTarget[i] < startAddress[i]) {
                isBeforeEndAddress = true;
                break;
              } else if (addressTarget[i] > startAddress[i]) {
                break;
              }
            }
            return isAfterStartAddress && isBeforeEndAddress;
          }
        }
      },
      deletesection: {
        execCommand: function(cmd, section, keepChildren) {
          var me = this;

          if (!section) return;

          function getNodeFromAddress(startAddress) {
            var current = me.body;
            for (var i = 0; i < startAddress.length; i++) {
              if (!current.childNodes) return null;
              current = current.childNodes[startAddress[i]];
            }
            return current;
          }

          var startNode = getNodeFromAddress(section.startAddress),
            endNode = getNodeFromAddress(section.endAddress),
            current = startNode,
            nextNode;

          if (!keepChildren) {
            while (
              current &&
              domUtils.inDoc(endNode, me.document) &&
              !(
                domUtils.getPosition(current, endNode) &
                domUtils.POSITION_FOLLOWING
              )
            ) {
              nextNode = current.nextSibling;
              domUtils.remove(current);
              current = nextNode;
            }
          } else {
            domUtils.remove(current);
          }

          me.fireEvent("updateSections");
        }
      },
      selectsection: {
        execCommand: function(cmd, section) {
          if (!section && !section.dom) return false;
          var me = this,
            range = me.selection.getRange(),
            address = {
              startAddress: utils.clone(section.startAddress, []),
              endAddress: utils.clone(section.endAddress, [])
            };
          address.endAddress[address.endAddress.length - 1]++;
          range.moveToAddress(address).select().scrollToView();
          return true;
        },
        notNeedUndo: true
      },
      scrolltosection: {
        execCommand: function(cmd, section) {
          if (!section && !section.dom) return false;
          var me = this,
            range = me.selection.getRange(),
            address = {
              startAddress: section.startAddress,
              endAddress: section.endAddress
            };
          address.endAddress[address.endAddress.length - 1]++;
          range.moveToAddress(address).scrollToView();
          return true;
        },
        notNeedUndo: true
      }
    }
  };
});


// plugins/simpleupload.js
/**
 * @description
 * 简单上传:点击按钮,直接选择文件上传
 * @author Jinqn
 * @date 2014-03-31
 */
UE.plugin.register("simpleupload", function () {
  var me = this,
    isLoaded = false,
    containerBtn;

  function initUploadBtn() {
    var w = containerBtn.offsetWidth || 20,
      h = containerBtn.offsetHeight || 20,
      btnIframe = document.createElement("iframe"),
      btnStyle =
        "display:block;width:" +
        w +
        "px;height:" +
        h +
        "px;overflow:hidden;border:0;margin:0;padding:0;position:absolute;top:0;left:0;filter:alpha(opacity=0);-moz-opacity:0;-khtml-opacity: 0;opacity: 0;cursor:pointer;";

    domUtils.on(btnIframe, "load", function () {
      var timestrap = (+new Date()).toString(36),
        wrapper,
        btnIframeDoc,
        btnIframeBody;

      var imageCompressEnable = me.getOpt('imageCompressEnable'),
        imageMaxSize = me.getOpt('imageMaxSize'),
        imageCompressBorder = me.getOpt('imageCompressBorder');
      // console.log('simpleupload.compress',imageCompressEnable, imageMaxSize, imageCompressBorder);

      btnIframeDoc =
        btnIframe.contentDocument || btnIframe.contentWindow.document;
      btnIframeBody = btnIframeDoc.body;
      wrapper = btnIframeDoc.createElement("div");

      wrapper.innerHTML =
        '<form id="edui_form_' +
        timestrap +
        '" target="edui_iframe_' +
        timestrap +
        '" method="POST" enctype="multipart/form-data" action="' +
        me.getOpt("serverUrl") +
        '" ' +
        'style="' +
        btnStyle +
        '">' +
        '<input id="edui_input_' +
        timestrap +
        '" type="file" accept="image/*" name="' +
        me.options.imageFieldName +
        '" ' +
        'style="' +
        btnStyle +
        '">' +
        "</form>" +
        '<iframe id="edui_iframe_' +
        timestrap +
        '" name="edui_iframe_' +
        timestrap +
        '" style="display:none;width:0;height:0;border:0;margin:0;padding:0;position:absolute;"></iframe>';

      wrapper.className = "edui-" + me.options.theme;
      wrapper.id = me.ui.id + "_iframeupload";
      btnIframeBody.style.cssText = btnStyle;
      btnIframeBody.style.width = w + "px";
      btnIframeBody.style.height = h + "px";
      btnIframeBody.appendChild(wrapper);

      if (btnIframeBody.parentNode) {
        btnIframeBody.parentNode.style.width = w + "px";
        btnIframeBody.parentNode.style.height = w + "px";
      }

      var form = btnIframeDoc.getElementById("edui_form_" + timestrap);
      var input = btnIframeDoc.getElementById("edui_input_" + timestrap);
      var iframe = btnIframeDoc.getElementById("edui_iframe_" + timestrap);

      domUtils.on(input, "change", function () {
        if (!input.value) return;

        var loadingId = "loading_" + (+new Date()).toString(36);
        var params = utils.serializeParam(me.queryCommandValue("serverparam")) || "";
        var imageActionUrl = me.getActionUrl(me.getOpt("imageActionName"));
        var allowFiles = me.getOpt("imageAllowFiles");
        var targetActionUrl = utils.formatUrl(imageActionUrl + (imageActionUrl.indexOf("?") === -1 ? "?" : "&") + params);

        me.focus();
        me.execCommand(
          "inserthtml",
          '<img class="loadingclass" id="' +
          loadingId +
          '" src="' +
          me.options.themePath +
          me.options.theme +
          '/images/spacer.gif">'
        );

        function showErrorLoader(title) {
          if (loadingId) {
            var loader = me.document.getElementById(loadingId);
            loader && domUtils.remove(loader);
            me.fireEvent("showmessage", {
              id: loadingId,
              content: title,
              type: "error",
              timeout: 4000
            });
          }
          alert(title);
        }

        // 判断后端配置是否没有加载成功
        if (!me.getOpt("imageActionName")) {
          showErrorLoader(me.getLang("autoupload.errorLoadConfig"));
          return;
        }

        // 判断文件格式是否错误
        var filename = input.value, fileext = filename ? filename.substr(filename.lastIndexOf(".")) : "";
        if (
          !fileext ||
          (allowFiles &&
            (allowFiles.join("") + ".").indexOf(fileext.toLowerCase() + ".") === -1)
        ) {
          showErrorLoader(me.getLang("simpleupload.exceedTypeError"));
          return;
        }

        function callback(json, cb) {
          try {
            var link = me.options.imageUrlPrefix + json.url;
            if (json.state === "SUCCESS" && json.url) {
              var loader = me.document.getElementById(loadingId);
              domUtils.removeClasses(loader, "loadingclass");
              loader.setAttribute("src", link);
              loader.setAttribute("_src", link);
              loader.setAttribute("alt", json.original || "");
              loader.removeAttribute("id");
              me.fireEvent("contentchange");
            } else {
              showErrorLoader && showErrorLoader(json.state);
            }
          } catch (er) {
            showErrorLoader &&
            showErrorLoader(me.getLang("simpleupload.loadError"));
          }
          form.reset();
          cb && cb();
        }

        function uploadDirect() {
          domUtils.on(iframe, "load", function () {
            var body = (iframe.contentDocument || iframe.contentWindow.document).body;
            var result = body.innerText || body.textContent || "";
            var json;
            try {
              json = new Function("return " + result)();
              if (!json) {
                json = {state: 'ERROR:UPLOAD_RESULT_EMPTY'};
              }
            } catch (e) {
              json = {state: 'ERROR:UPLOAD_FAIL'};
            }
            callback(json, function () {
              domUtils.un(iframe, "load", callback);
            });
          });
          form.action = targetActionUrl;
          form.submit();
        }

        var compressSupport = false;
        if (imageCompressEnable) {
          compressSupport = true;
        }
        // 类型映射
        var imageMimeMap = {
          '.jpg': 'image/jpeg',
          '.jpeg': 'image/jpeg',
          '.png': 'image/png',
          '.gif': 'image/gif',
        }
        var imageMimeType
        if (fileext.toLowerCase() in imageMimeMap) {
          imageMimeType = imageMimeMap[fileext.toLowerCase()]
        } else {
          compressSupport = false;
        }
        var fileBaseName = input.files[0].name;
        if (compressSupport) {
          var img = new Image();
          img.onload = function () {
            var w = img.width, h = img.height, rate;
            if (w > imageCompressBorder) {
              rate = w / imageCompressBorder;
              w = imageCompressBorder;
              h = Math.round(h / rate);
            } else if (h > imageCompressBorder) {
              rate = h / imageCompressBorder;
              h = imageCompressBorder;
              w = Math.round(w / rate);
            }
            var canvas = document.createElement('canvas');
            var ctx = canvas.getContext('2d');
            canvas.setAttribute('width', w);
            canvas.setAttribute('height', h);
            ctx.drawImage(img, 0, 0, w, h);

            canvas.toBlob(function (blob) {
              var xhr = new XMLHttpRequest(), fd = new FormData()
              fd.append(me.options.imageFieldName, blob, fileBaseName);
              xhr.open("post", targetActionUrl, true);
              xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
              xhr.addEventListener("load", function (e) {
                try {
                  var json = new Function("return " + utils.trim(e.target.response))();
                  if (json.state === "SUCCESS" && json.url) {
                    callback(json);
                  } else {
                    showErrorLoader(json.state);
                  }
                } catch (er) {
                  showErrorLoader(me.getLang("autoupload.loadError"));
                }
              });
              xhr.send(fd);

            }, imageMimeType, 0.8);

          };
          img.src = URL.createObjectURL(input.files[0]);

        } else {
          // 无需压缩直接上传
          uploadDirect();
        }

      });

      var stateTimer;
      me.addListener("selectionchange", function () {
        clearTimeout(stateTimer);
        stateTimer = setTimeout(function () {
          var state = me.queryCommandState("simpleupload");
          if (state == -1) {
            input.disabled = "disabled";
          } else {
            input.disabled = false;
          }
        }, 400);
      });
      isLoaded = true;
    });

    btnIframe.style.cssText = btnStyle;
    containerBtn.appendChild(btnIframe);
  }

  return {
    bindEvents: {
      ready: function () {
        //设置loading的样式
        utils.cssRule(
          "loading",
          ".loadingclass{display:inline-block;cursor:default;background: url('" +
          this.options.themePath +
          this.options.theme +
          "/images/loading.gif') no-repeat center center transparent;border-radius:3px;outline:1px solid #EEE;margin-right:1px;height:22px;width:22px;}\n" +
          ".loaderrorclass{display:inline-block;cursor:default;background: url('" +
          this.options.themePath +
          this.options.theme +
          "/images/loaderror.png') no-repeat center center transparent;border-radius:3px;outline:1px solid #EEE;margin-right:1px;height:22px;width:22px;" +
          "}",
          this.document
        );
      },
      /* 初始化简单上传按钮 */
      simpleuploadbtnready: function (type, container) {
        containerBtn = container;
        me.afterConfigReady(initUploadBtn);
      }
    },
    outputRule: function (root) {
      utils.each(root.getNodesByTagName("img"), function (n) {
        if (/\b(loaderrorclass)|(bloaderrorclass)\b/.test(n.getAttr("class"))) {
          n.parentNode.removeChild(n);
        }
      });
    },
    commands: {
      simpleupload: {
        queryCommandState: function () {
          return isLoaded ? 0 : -1;
        }
      }
    }
  };
});


// plugins/serverparam.js
/**
 * 服务器提交的额外参数列表设置插件
 * @file
 * @since 1.2.6.1
 */
UE.plugin.register("serverparam", function() {
  var me = this,
    serverParam = {};

  return {
    commands: {
      /**
             * 修改服务器提交的额外参数列表,清除所有项
             * @command serverparam
             * @method execCommand
             * @param { String } cmd 命令字符串
             * @example
             * ```javascript
             * editor.execCommand('serverparam');
             * editor.queryCommandValue('serverparam'); //返回空
             * ```
             */
      /**
             * 修改服务器提交的额外参数列表,删除指定项
             * @command serverparam
             * @method execCommand
             * @param { String } cmd 命令字符串
             * @param { String } key 要清除的属性
             * @example
             * ```javascript
             * editor.execCommand('serverparam', 'name'); //删除属性name
             * ```
             */
      /**
             * 修改服务器提交的额外参数列表,使用键值添加项
             * @command serverparam
             * @method execCommand
             * @param { String } cmd 命令字符串
             * @param { String } key 要添加的属性
             * @param { String } value 要添加属性的值
             * @example
             * ```javascript
             * editor.execCommand('serverparam', 'name', 'hello');
             * editor.queryCommandValue('serverparam'); //返回对象 {'name': 'hello'}
             * ```
             */
      /**
             * 修改服务器提交的额外参数列表,传入键值对对象添加多项
             * @command serverparam
             * @method execCommand
             * @param { String } cmd 命令字符串
             * @param { Object } key 传入的键值对对象
             * @example
             * ```javascript
             * editor.execCommand('serverparam', {'name': 'hello'});
             * editor.queryCommandValue('serverparam'); //返回对象 {'name': 'hello'}
             * ```
             */
      /**
             * 修改服务器提交的额外参数列表,使用自定义函数添加多项
             * @command serverparam
             * @method execCommand
             * @param { String } cmd 命令字符串
             * @param { Function } key 自定义获取参数的函数
             * @example
             * ```javascript
             * editor.execCommand('serverparam', function(editor){
             *     return {'key': 'value'};
             * });
             * editor.queryCommandValue('serverparam'); //返回对象 {'key': 'value'}
             * ```
             */

      /**
             * 获取服务器提交的额外参数列表
             * @command serverparam
             * @method queryCommandValue
             * @param { String } cmd 命令字符串
             * @example
             * ```javascript
             * editor.queryCommandValue( 'serverparam' ); //返回对象 {'key': 'value'}
             * ```
             */
      serverparam: {
        execCommand: function(cmd, key, value) {
          if (key === undefined || key === null) {
            //不传参数,清空列表
            serverParam = {};
          } else if (utils.isString(key)) {
            //传入键值
            if (value === undefined || value === null) {
              delete serverParam[key];
            } else {
              serverParam[key] = value;
            }
          } else if (utils.isObject(key)) {
            //传入对象,覆盖列表项
            utils.extend(serverParam, key, false);
          } else if (utils.isFunction(key)) {
            //传入函数,添加列表项
            utils.extend(serverParam, key(), false);
          }
        },
        queryCommandValue: function() {
          return serverParam || {};
        }
      }
    }
  };
});


// plugins/insertfile.js
/**
 * 插入附件
 */
UE.plugin.register("insertfile", function() {
  var me = this;

  function getFileIcon(url) {
    var ext = url.substr(url.lastIndexOf(".") + 1).toLowerCase(),
      maps = {
          "doc":"doc.svg",
          "docx":"docx.svg",
          "gif":"gif.svg",
          "jpeg":"jpeg.svg",
          "jpg":"jpg.svg",
          "mp3":"mp3.svg",
          "mp4":"mp4.svg",
          "pdf":"pdf.svg",
          "png":"png.svg",
          "ppt":"ppt.svg",
          "pptx":"pptx.svg",
          "rar":"rar.svg",
          "torrent":"torrent.svg",
          "txt":"txt.svg",
          "unknown":"unknown.svg",
          "xls":"xls.svg",
          "xlsx":"xlsx.svg",
          "zip":"zip.svg",
      };
    return maps[ext] ? maps[ext] : maps["unknown"];
  }

  return {
    commands: {
      insertfile: {
        execCommand: function(command, filelist) {
          filelist = utils.isArray(filelist) ? filelist : [filelist];

          if (me.fireEvent("beforeinsertfile", filelist) === true) {
            return;
          }


          //console.log('themePath',  );
          var i,
            item,
            icon,
            title,
            html = "",
            URL = me.getOpt("UEDITOR_HOME_URL"),
            iconDir = me.options.themePath + me.options.theme + "/exts/";
          for (i = 0; i < filelist.length; i++) {
            item = filelist[i];
            icon = iconDir + getFileIcon(item.url);
            title =
              item.title || item.url.substr(item.url.lastIndexOf("/") + 1);
            html +=
              '<p>' +
              '<a style="background:#EEE;padding:10px;border-radius:5px;line-height:1.5em;display:inline-flex;align-items:center;" href="' +
                item.url +
                '" title="' +
                title +
                '" target="_blank">' +
               '<img style="vertical-align:middle;margin-right:0.5em;height:1.5em;" src="' + icon + '" _src="' + icon + '" />' +
               '<span style="color:#111111;line-height:1.5em;flex-grow:1;">' +
                title +
               "</span>" +
              "</a>" +
              "</p>";
          }
          me.execCommand("insertHtml", html);

          me.fireEvent("afterinsertfile", filelist);
        }
      }
    }
  };
});


// plugins/markdown-shortcut.js
UE.plugins["markdown-shortcut"] = function () {

  if (!UE.browser.chrome) {
    return;
  }

  const me = this;

  const getCleanHtml = function (node) {
    let html = node.innerHTML
    html = html.replace(/[\u200b]*/g, '')
    return html
  }

  let shortCuts = [];
  // 注册 H1-H6 快捷键
  for (let i = 1; i <= 6; i++) {
    const regExp = new RegExp('^\\t?' + Array(i + 1).join('#') + '(\\s|&nbsp;)');
    (function (command) {
      shortCuts.push({
        name: 'Head' + i,
        tagName: ['P'],
        key: [' '],
        offset: [i + 1, i + 2],
        match: [regExp],
        callback: function (param) {
          me.__hasEnterExecCommand = true;
          me.execCommand('paragraph', command);
          let range = me.selection.getRange();
          let node = range.startContainer;
          let html = getCleanHtml(node)
          html = html.replace(regExp, '');
          if (!html) {
            html = domUtils.fillChar;
          }
          node.innerHTML = html;
          me.__hasEnterExecCommand = false;
        }
      })
    })('h' + i);
  }

  me.on("ready", function () {

    domUtils.on(me.body, 'keyup', function (e) {
      let range = me.selection.getRange();
      if (range.endOffset !== range.startOffset) {
        return;
      }
      let key = e.key;
      let offset = range.startOffset;
      const node = range.startContainer.parentNode;
      let html = getCleanHtml(node);
      let tagName = node.tagName;
      // console.log('keyup', [node, range, tagName, key, offset, html]);
      for (let s of shortCuts) {
        if (!s.tagName.includes(tagName)) {
          continue;
        }
        if (!s.key.includes(key)) {
          continue;
        }
        if (!s.offset.includes(offset)) {
          continue;
        }
        for (let m of s.match) {
          let match = html.match(m);
          // console.log('keyup', [html, m, match, s.name]);
          if (match) {
            s.callback({
              node: node,
            });
            break;
          }
        }
      }
    });

  });

};


// plugins/quick-operate.js
UE.plugins["quick-operate"] = function () {

  if (!UE.browser.chrome) {
    return;
  }
  return;

  let me = this;
  const uiUtils = UE.ui.uiUtils;

  me.on("ready", function () {
    let quickOperate = new UE.ui.QuickOperate({
      // items: contextItems,
      className: "edui-quick-operate",
      editor: me
    });
    quickOperate.render();

    let quickOperateNode = {
      root: null,
      target: null,
    }
    domUtils.on(quickOperate.el, 'mouseenter', function (evt) {
      quickOperateNode.root && quickOperateNode.root.classList && quickOperateNode.root.classList.add('edui-quick-operate-active');
    });
    domUtils.on(quickOperate.el, 'mouseleave', function (evt) {
      quickOperateNode.root && quickOperateNode.root.classList && quickOperateNode.root.classList.remove('edui-quick-operate-active');
    });
    domUtils.on(me.body, "mouseout", function (evt) {
      // quickOperate.hide();
    });
    domUtils.on(me.body, "mouseover", function (evt) {
      const node = evt.target
      let rootNode = node;
      for (; rootNode.parentNode && rootNode.parentNode.tagName !== 'BODY';) {
        rootNode = rootNode.parentNode;
      }
      quickOperateNode.root = rootNode
      quickOperateNode.target = node
      // me.body.querySelectorAll('& > *').forEach(item => {
      //   item.classList.remove('edui-quick-operate-active');
      // });
      // rootNode.classList.add('edui-quick-operate-active');
      const rect = node.getBoundingClientRect();
      const offset = uiUtils.getClientRect(node)
      offset.left = offset.left - 55
      // console.log('mouseover', rect, node, offset);
      // let offset = uiUtils.getViewportOffsetByEvent(evt);
      // console.log('quickOperate', quickOperate);
      quickOperate.showAt(offset);
    });

  });

};


// ui/ui.js
var baidu = baidu || {};
baidu.editor = baidu.editor || {};
UE.ui = baidu.editor.ui = {};


// ui/uiutils.js
(function() {
  var browser = baidu.editor.browser,
    domUtils = baidu.editor.dom.domUtils;

  var magic = "$EDITORUI";
  var root = (window[magic] = {});
  var uidMagic = "ID" + magic;
  var uidCount = 0;

  var uiUtils = (baidu.editor.ui.uiUtils = {
    uid: function(obj) {
      return obj ? obj[uidMagic] || (obj[uidMagic] = ++uidCount) : ++uidCount;
    },
    hook: function(fn, callback) {
      var dg;
      if (fn && fn._callbacks) {
        dg = fn;
      } else {
        dg = function() {
          var q;
          if (fn) {
            q = fn.apply(this, arguments);
          }
          var callbacks = dg._callbacks;
          var k = callbacks.length;
          while (k--) {
            var r = callbacks[k].apply(this, arguments);
            if (q === undefined) {
              q = r;
            }
          }
          return q;
        };
        dg._callbacks = [];
      }
      dg._callbacks.push(callback);
      return dg;
    },
    createElementByHtml: function(html) {
      var el = document.createElement("div");
      el.innerHTML = html;
      el = el.firstChild;
      el.parentNode.removeChild(el);
      return el;
    },
    getViewportElement: function() {
      return browser.ie && browser.quirks
        ? document.body
        : document.documentElement;
    },
    getClientRect: function(element) {
      var bcr;
      //trace  IE6下在控制编辑器显隐时可能会报错，catch一下
      try {
        bcr = element.getBoundingClientRect();
      } catch (e) {
        bcr = { left: 0, top: 0, height: 0, width: 0 };
      }
      var rect = {
        left: Math.round(bcr.left),
        top: Math.round(bcr.top),
        height: Math.round(bcr.bottom - bcr.top),
        width: Math.round(bcr.right - bcr.left)
      };
      var doc;
      while (
        (doc = element.ownerDocument) !== document &&
        (element = domUtils.getWindow(doc).frameElement)
      ) {
        bcr = element.getBoundingClientRect();
        rect.left += bcr.left;
        rect.top += bcr.top;
      }
      rect.bottom = rect.top + rect.height;
      rect.right = rect.left + rect.width;
      return rect;
    },
    getViewportRect: function() {
      var viewportEl = uiUtils.getViewportElement();
      var width = (window.innerWidth || viewportEl.clientWidth) | 0;
      var height = (window.innerHeight || viewportEl.clientHeight) | 0;
      return {
        left: 0,
        top: 0,
        height: height,
        width: width,
        bottom: height,
        right: width
      };
    },
    setViewportOffset: function(element, offset) {
      var rect;
      var fixedLayer = uiUtils.getFixedLayer();
      if (element.parentNode === fixedLayer) {
        element.style.left = offset.left + "px";
        element.style.top = offset.top + "px";
      } else {
        domUtils.setViewportOffset(element, offset);
      }
    },
    getEventOffset: function(evt) {
      var el = evt.target || evt.srcElement;
      var rect = uiUtils.getClientRect(el);
      var offset = uiUtils.getViewportOffsetByEvent(evt);
      return {
        left: offset.left - rect.left,
        top: offset.top - rect.top
      };
    },
    getViewportOffsetByEvent: function(evt) {
      var el = evt.target || evt.srcElement;
      var frameEl = domUtils.getWindow(el).frameElement;
      var offset = {
        left: evt.clientX,
        top: evt.clientY
      };
      if (frameEl && el.ownerDocument !== document) {
        var rect = uiUtils.getClientRect(frameEl);
        offset.left += rect.left;
        offset.top += rect.top;
      }
      return offset;
    },
    setGlobal: function(id, obj) {
      root[id] = obj;
      return magic + '["' + id + '"]';
    },
    unsetGlobal: function(id) {
      delete root[id];
    },
    copyAttributes: function(tgt, src) {
      var attributes = src.attributes;
      var k = attributes.length;
      while (k--) {
        var attrNode = attributes[k];
        if (
          attrNode.nodeName != "style" &&
          attrNode.nodeName != "class" &&
          (!browser.ie || attrNode.specified)
        ) {
          tgt.setAttribute(attrNode.nodeName, attrNode.nodeValue);
        }
      }
      if (src.className) {
        domUtils.addClass(tgt, src.className);
      }
      if (src.style.cssText) {
        tgt.style.cssText += ";" + src.style.cssText;
      }
    },
    removeStyle: function(el, styleName) {
      if (el.style.removeProperty) {
        el.style.removeProperty(styleName);
      } else if (el.style.removeAttribute) {
        el.style.removeAttribute(styleName);
      } else throw "";
    },
    contains: function(elA, elB) {
      return (
        elA &&
        elB &&
        (elA === elB
          ? false
          : elA.contains
            ? elA.contains(elB)
            : elA.compareDocumentPosition(elB) & 16)
      );
    },
    startDrag: function(evt, callbacks, doc) {
      var doc = doc || document;
      var startX = evt.clientX;
      var startY = evt.clientY;
      function handleMouseMove(evt) {
        var x = evt.clientX - startX;
        var y = evt.clientY - startY;
        callbacks.ondragmove(x, y, evt);
        if (evt.stopPropagation) {
          evt.stopPropagation();
        } else {
          evt.cancelBubble = true;
        }
      }
      if (doc.addEventListener) {
        function handleMouseUp(evt) {
          doc.removeEventListener("mousemove", handleMouseMove, true);
          doc.removeEventListener("mouseup", handleMouseUp, true);
          window.removeEventListener("mouseup", handleMouseUp, true);
          callbacks.ondragstop();
        }
        doc.addEventListener("mousemove", handleMouseMove, true);
        doc.addEventListener("mouseup", handleMouseUp, true);
        window.addEventListener("mouseup", handleMouseUp, true);

        evt.preventDefault();
      } else {
        var elm = evt.srcElement;
        elm.setCapture();
        function releaseCaptrue() {
          elm.releaseCapture();
          elm.detachEvent("onmousemove", handleMouseMove);
          elm.detachEvent("onmouseup", releaseCaptrue);
          elm.detachEvent("onlosecaptrue", releaseCaptrue);
          callbacks.ondragstop();
        }
        elm.attachEvent("onmousemove", handleMouseMove);
        elm.attachEvent("onmouseup", releaseCaptrue);
        elm.attachEvent("onlosecaptrue", releaseCaptrue);
        evt.returnValue = false;
      }
      callbacks.ondragstart();
    },
    getFixedLayer: function() {
      var layer = document.getElementById("edui_fixedlayer");
      if (layer == null) {
        layer = document.createElement("div");
        layer.id = "edui_fixedlayer";
        document.body.appendChild(layer);
        if (browser.ie && browser.version <= 8) {
          layer.style.position = "absolute";
          bindFixedLayer();
          setTimeout(updateFixedOffset);
        } else {
          layer.style.position = "fixed";
        }
        layer.style.left = "0";
        layer.style.top = "0";
        layer.style.width = "0";
        layer.style.height = "0";
        layer.style.margin = "0";
      }
      return layer;
    },
    makeUnselectable: function(element) {
      if (browser.opera || (browser.ie && browser.version < 9)) {
        element.unselectable = "on";
        if (element.hasChildNodes()) {
          for (var i = 0; i < element.childNodes.length; i++) {
            if (element.childNodes[i].nodeType == 1) {
              uiUtils.makeUnselectable(element.childNodes[i]);
            }
          }
        }
      } else {
        if (element.style.MozUserSelect !== undefined) {
          element.style.MozUserSelect = "none";
        } else if (element.style.WebkitUserSelect !== undefined) {
          element.style.WebkitUserSelect = "none";
        } else if (element.style.KhtmlUserSelect !== undefined) {
          element.style.KhtmlUserSelect = "none";
        }
      }
    }
  });
  function updateFixedOffset() {
    var layer = document.getElementById("edui_fixedlayer");
    uiUtils.setViewportOffset(layer, {
      left: 0,
      top: 0
    });
    //        layer.style.display = 'none';
    //        layer.style.display = 'block';

    //#trace: 1354
    //        setTimeout(updateFixedOffset);
  }
  function bindFixedLayer(adjOffset) {
    domUtils.on(window, "scroll", updateFixedOffset);
    domUtils.on(
      window,
      "resize",
      baidu.editor.utils.defer(updateFixedOffset, 0, true)
    );
  }
})();


// ui/uibase.js
(function() {
  var utils = baidu.editor.utils,
    uiUtils = baidu.editor.ui.uiUtils,
    EventBase = baidu.editor.EventBase,
    UIBase = (baidu.editor.ui.UIBase = function() {});

  UIBase.prototype = {
    el: null,
    className: "",
    uiName: "",
    initOptions: function(options) {
      var me = this;
      for (var k in options) {
        me[k] = options[k];
      }
      this.id = this.id || "edui" + uiUtils.uid();
    },
    initUIBase: function() {
      this._globalKey = utils.unhtml(uiUtils.setGlobal(this.id, this));
    },
    render: function(holder) {
      var html = this.renderHtml();
      var el = uiUtils.createElementByHtml(html);

      //by xuheng 给每个node添加class
      var list = domUtils.getElementsByTagName(el, "*");
      var theme = "edui-" + (this.theme || this.editor.options.theme);
      var layer = document.getElementById("edui_fixedlayer");
      for (var i = 0, node; (node = list[i++]); ) {
        domUtils.addClass(node, theme);
      }
      domUtils.addClass(el, theme);
      if (layer) {
        layer.className = "";
        domUtils.addClass(layer, theme);
      }

      var seatEl = this.getDom();
      if (seatEl != null) {
        seatEl.parentNode.replaceChild(el, seatEl);
        uiUtils.copyAttributes(el, seatEl);
      } else {
        if (typeof holder == "string") {
          holder = document.getElementById(holder);
        }
        holder = holder || uiUtils.getFixedLayer();
        // console.log('Uibase.render',holder,el);
        domUtils.addClass(holder, theme);
        holder.appendChild(el);
      }
      this.el = el;
      this.postRender();
    },
    getDom: function(name) {
      if (!name) {
        return document.getElementById(this.id);
      } else {
        return document.getElementById(this.id + "_" + name);
      }
    },
    postRender: function() {
      this.fireEvent("postrender");
    },
    getHtmlTpl: function() {
      return "";
    },
    formatHtml: function(tpl) {
      var prefix = "edui-" + this.uiName;
      return tpl
        .replace(/##/g, this.id)
        .replace(/%%-/g, this.uiName ? prefix + "-" : "")
        .replace(/%%/g, (this.uiName ? prefix : "") + " " + this.className)
        .replace(/\$\$/g, this._globalKey);
    },
    renderHtml: function() {
      return this.formatHtml(this.getHtmlTpl());
    },
    dispose: function() {
      var box = this.getDom();
      if (box) baidu.editor.dom.domUtils.remove(box);
      uiUtils.unsetGlobal(this.id);
    }
  };
  utils.inherits(UIBase, EventBase);
})();


// ui/separator.js
(function() {
  var utils = baidu.editor.utils,
    UIBase = baidu.editor.ui.UIBase,
    Separator = (baidu.editor.ui.Separator = function(options) {
      this.initOptions(options);
      this.initSeparator();
    });
  Separator.prototype = {
    uiName: "separator",
    initSeparator: function() {
      this.initUIBase();
    },
    getHtmlTpl: function() {
      return '<div id="##" class="edui-box %%"></div>';
    }
  };
  utils.inherits(Separator, UIBase);
})();


// ui/mask.js
///import core
///import uicore
(function() {
  var utils = baidu.editor.utils,
    domUtils = baidu.editor.dom.domUtils,
    UIBase = baidu.editor.ui.UIBase,
    uiUtils = baidu.editor.ui.uiUtils;

  var Mask = (baidu.editor.ui.Mask = function(options) {
    this.initOptions(options);
    this.initUIBase();
  });
  Mask.prototype = {
    getHtmlTpl: function() {
      return '<div id="##" class="edui-mask %%" onclick="return $$._onClick(event, this);" onmousedown="return $$._onMouseDown(event, this);"></div>';
    },
    postRender: function() {
      var me = this;
      domUtils.on(window, "resize", function() {
        setTimeout(function() {
          if (!me.isHidden()) {
            me._fill();
          }
        });
      });
    },
    show: function(zIndex) {
      this._fill();
      this.getDom().style.display = "";
      this.getDom().style.zIndex = zIndex;
    },
    hide: function() {
      this.getDom().style.display = "none";
      this.getDom().style.zIndex = "";
    },
    isHidden: function() {
      return this.getDom().style.display == "none";
    },
    _onMouseDown: function() {
      return false;
    },
    _onClick: function(e, target) {
      this.fireEvent("click", e, target);
    },
    _fill: function() {
      var el = this.getDom();
      var vpRect = uiUtils.getViewportRect();
      el.style.width = vpRect.width + "px";
      el.style.height = vpRect.height + "px";
    }
  };
  utils.inherits(Mask, UIBase);
})();


// ui/popup.js
///import core
///import uicore
(function() {
  var utils = baidu.editor.utils,
    uiUtils = baidu.editor.ui.uiUtils,
    domUtils = baidu.editor.dom.domUtils,
    UIBase = baidu.editor.ui.UIBase,
    Popup = (baidu.editor.ui.Popup = function(options) {
      this.initOptions(options);
      this.initPopup();
    });

  var allPopups = [];
  function closeAllPopup(evt, el) {
    for (var i = 0; i < allPopups.length; i++) {
      var pop = allPopups[i];
      if (!pop.isHidden()) {
        if (pop.queryAutoHide(el) !== false) {
          if (
            evt &&
            /scroll/gi.test(evt.type) &&
            pop.className == "edui-wordpastepop"
          )
            return;
          pop.hide();
        }
      }
    }

    if (allPopups.length) pop.editor.fireEvent("afterhidepop");
  }

  Popup.postHide = closeAllPopup;

  var ANCHOR_CLASSES = [
    "edui-anchor-topleft",
    "edui-anchor-topright",
    "edui-anchor-bottomleft",
    "edui-anchor-bottomright"
  ];
  Popup.prototype = {
    SHADOW_RADIUS: 5,
    content: null,
    _hidden: false,
    autoRender: true,
    canSideLeft: true,
    canSideUp: true,
    initPopup: function() {
      this.initUIBase();
      allPopups.push(this);
    },
    getHtmlTpl: function() {
      return (
        '<div id="##" class="edui-popup %%" onmousedown="return false;">' +
        ' <div id="##_body" class="edui-popup-body">' +
        ' <iframe style="position:absolute;z-index:-1;left:0;top:0;background-color: transparent;" frameborder="0" width="100%" height="100%" src="about:blank"></iframe>' +
        ' <div class="edui-shadow"></div>' +
        ' <div id="##_content" class="edui-popup-content">' +
        this.getContentHtmlTpl() +
        "  </div>" +
        " </div>" +
        "</div>"
      );
    },
    getContentHtmlTpl: function() {
      if (this.content) {
        if (typeof this.content == "string") {
          return this.content;
        }
        return this.content.renderHtml();
      } else {
        return "";
      }
    },
    _UIBase_postRender: UIBase.prototype.postRender,
    postRender: function() {
      if (this.content instanceof UIBase) {
        this.content.postRender();
      }

      //捕获鼠标滚轮
      if (this.captureWheel && !this.captured) {
        this.captured = true;

        var winHeight =
          (document.documentElement.clientHeight ||
            document.body.clientHeight) - 80,
          _height = this.getDom().offsetHeight,
          _top = uiUtils.getClientRect(this.combox.getDom()).top,
          content = this.getDom("content"),
          ifr = this.getDom("body").getElementsByTagName("iframe"),
          me = this;

        ifr.length && (ifr = ifr[0]);

        while (_top + _height > winHeight) {
          _height -= 30;
        }
        content.style.height = _height + "px";
        //同步更改iframe高度
        ifr && (ifr.style.height = _height + "px");

        //阻止在combox上的鼠标滚轮事件, 防止用户的正常操作被误解
        domUtils.on(
            content,
            "onmousewheel" in document.body ? "mousewheel" : "DOMMouseScroll",
            function(e) {
              if (e.preventDefault) {
                e.preventDefault();
              } else {
                e.returnValue = false;
              }

              if (e.wheelDelta) {
                content.scrollTop -= e.wheelDelta / 120 * 60;
              } else {
                content.scrollTop -= e.detail / -3 * 60;
              }
            }
        );
      }
      this.fireEvent("postRenderAfter");
      this.hide(true);
      this._UIBase_postRender();
    },
    _doAutoRender: function() {
      if (!this.getDom() && this.autoRender) {
        this.render();
      }
    },
    mesureSize: function() {
      var box = this.getDom("content");
      return uiUtils.getClientRect(box);
    },
    fitSize: function() {
      // console.log('fitSize.popup')
      if (this.captureWheel && this.sized) {
        return this.__size;
      }
      this.sized = true;
      var popBodyEl = this.getDom("body");
      popBodyEl.style.width = "";
      popBodyEl.style.height = "";
      var size = this.mesureSize();
      if (this.captureWheel) {
        popBodyEl.style.width = -(-20 - size.width) + "px";
        var height = parseInt(this.getDom("content").style.height, 10);
        !window.isNaN(height) && (size.height = height);
      } else {
        popBodyEl.style.width = size.width + "px";
      }
      popBodyEl.style.height = size.height + "px";
      this.__size = size;
      this.captureWheel && (this.getDom("content").style.overflow = "auto");
      return size;
    },
    showAnchor: function(element, hoz) {
      this.showAnchorRect(uiUtils.getClientRect(element), hoz);
    },
    showAnchorRect: function(rect, hoz, adj) {
      this._doAutoRender();
      var vpRect = uiUtils.getViewportRect();
      this.getDom().style.visibility = "hidden";
      this._show();
      var popSize = this.fitSize();

      var sideLeft, sideUp, left, top;
      if (hoz) {
        sideLeft =
          this.canSideLeft &&
          (rect.right + popSize.width > vpRect.right &&
            rect.left > popSize.width);
        sideUp =
          this.canSideUp &&
          (rect.top + popSize.height > vpRect.bottom &&
            rect.bottom > popSize.height);
        left = sideLeft ? rect.left - popSize.width : rect.right;
        top = sideUp ? rect.bottom - popSize.height : rect.top;
      } else {
        sideLeft =
          this.canSideLeft &&
          (rect.right + popSize.width > vpRect.right &&
            rect.left > popSize.width);
        sideUp =
          this.canSideUp &&
          (rect.top + popSize.height > vpRect.bottom &&
            rect.bottom > popSize.height);
        left = sideLeft ? rect.right - popSize.width : rect.left;
        top = sideUp ? rect.top - popSize.height : rect.bottom;
      }
      if(!sideUp){
        if(top + popSize.height > vpRect.bottom){
          top = vpRect.bottom - popSize.height
        }
      }
      // console.log('popup.showAnchorRect', vpRect, rect, hoz, sideUp, sideLeft, left, top);

      var popEl = this.getDom();
      uiUtils.setViewportOffset(popEl, {
        left: left,
        top: top
      });
      domUtils.removeClasses(popEl, ANCHOR_CLASSES);
      popEl.className +=
        " " + ANCHOR_CLASSES[(sideUp ? 1 : 0) * 2 + (sideLeft ? 1 : 0)];
      if (this.editor) {
        popEl.style.zIndex = this.editor.container.style.zIndex * 1 + 10;
        baidu.editor.ui.uiUtils.getFixedLayer().style.zIndex =
          popEl.style.zIndex - 1;
      }
      this.getDom().style.visibility = "visible";
    },
    showAt: function(offset) {
      var left = offset.left;
      var top = offset.top;
      var rect = {
        left: left,
        top: top,
        right: left,
        bottom: top,
        height: 0,
        width: 0
      };
      this.showAnchorRect(rect, false, true);
    },
    _show: function() {
      if (this._hidden) {
        var box = this.getDom();
        box.style.display = "";
        this._hidden = false;
        //                if (box.setActive) {
        //                    box.setActive();
        //                }
        this.fireEvent("show");
      }
    },
    isHidden: function() {
      return this._hidden;
    },
    show: function() {
      this._doAutoRender();
      this._show();
    },
    hide: function(notNofity) {
      if (!this._hidden && this.getDom()) {
        this.getDom().style.display = "none";
        this._hidden = true;
        if (!notNofity) {
          this.fireEvent("hide");
        }
      }
    },
    queryAutoHide: function(el) {
      return !el || !uiUtils.contains(this.getDom(), el);
    }
  };
  utils.inherits(Popup, UIBase);

  domUtils.on(document, "mousedown", function(evt) {
    var el = evt.target || evt.srcElement;
    closeAllPopup(evt, el);
  });
  domUtils.on(window, "scroll", function(evt, el) {
    closeAllPopup(evt, el);
  });
})();


// ui/colorpicker.js
///import core
///import uicore
(function() {
  var utils = baidu.editor.utils,
    UIBase = baidu.editor.ui.UIBase,
    ColorPicker = (baidu.editor.ui.ColorPicker = function(options) {
      this.initOptions(options);
      this.noColorText = this.noColorText || this.editor.getLang("clearColor");
      this.initUIBase();
    });

  ColorPicker.prototype = {
    getHtmlTpl: function() {
      return genColorPicker(this.noColorText, this.editor);
    },
    _onTableClick: function(evt) {
      var tgt = evt.target || evt.srcElement;
      var color = tgt.getAttribute("data-color");
      if (color) {
        this.fireEvent("pickcolor", color);
      }
    },
    _onTableOver: function(evt) {
      var tgt = evt.target || evt.srcElement;
      var color = tgt.getAttribute("data-color");
      if (color) {
        this.getDom("preview").style.backgroundColor = color;
      }
    },
    _onTableOut: function() {
      this.getDom("preview").style.backgroundColor = "";
    },
    _onPickNoColor: function() {
      this.fireEvent("picknocolor");
    },
    _onColorSelect: function(evt) {
      var input = evt.target || evt.srcElement;
      var color = input.value;
      if (color) {
        this.fireEvent("pickcolor", color);
      }
    }
  };
  utils.inherits(ColorPicker, UIBase);

  var COLORS = ("ffffff,000000,eeece1,1f497d,4f81bd,c0504d,9bbb59,8064a2,4bacc6,f79646," +
    "f2f2f2,7f7f7f,ddd9c3,c6d9f0,dbe5f1,f2dcdb,ebf1dd,e5e0ec,dbeef3,fdeada," +
    "d8d8d8,595959,c4bd97,8db3e2,b8cce4,e5b9b7,d7e3bc,ccc1d9,b7dde8,fbd5b5," +
    "bfbfbf,3f3f3f,938953,548dd4,95b3d7,d99694,c3d69b,b2a2c7,92cddc,fac08f," +
    "a5a5a5,262626,494429,17365d,366092,953734,76923c,5f497a,31859b,e36c09," +
    "7f7f7f,0c0c0c,1d1b10,0f243e,244061,632423,4f6128,3f3151,205867,974806," +
    "c00000,ff0000,ffc000,ffff00,92d050,00b050,00b0f0,0070c0,002060,7030a0,").split(
    ","
  );

  function genColorPicker(noColorText, editor) {
    var html =
      '<div id="##" class="edui-colorpicker %%">' +
      '<div class="edui-colorpicker-topbar edui-clearfix">' +
      // '<div unselectable="on" id="##_preview" class="edui-colorpicker-preview"></div>' +
      '<div id="##_preview" class="edui-colorpicker-preview"><input type="color" id="##_input" onchange="$$._onColorSelect(event,this);" /></div>' +
      '<div unselectable="on" class="edui-colorpicker-nocolor" onclick="$$._onPickNoColor(event, this);">' +
      noColorText +
      "</div>" +
      "</div>" +
      '<table  class="edui-box" style="border-collapse: collapse;" onmouseover="$$._onTableOver(event, this);" onmouseout="$$._onTableOut(event, this);" onclick="return $$._onTableClick(event, this);" cellspacing="0" cellpadding="0">' +
      '<tr style="border-bottom: 1px solid #ddd;font-size: 13px;line-height: 25px;color:#39C;padding-top: 2px"><td colspan="10">' +
      editor.getLang("themeColor") +
      "</td> </tr>" +
      '<tr class="edui-colorpicker-tablefirstrow" >';
    for (var i = 0; i < COLORS.length; i++) {
      if (i && i % 10 === 0) {
        html +=
          "</tr>" +
          (i == 60
            ? '<tr style="border-bottom: 1px solid #ddd;font-size: 13px;line-height: 25px;color:#39C;"><td colspan="10">' +
                editor.getLang("standardColor") +
                "</td></tr>"
            : "") +
          "<tr" +
          (i == 60 ? ' class="edui-colorpicker-tablefirstrow"' : "") +
          ">";
      }
      html += i < 70
        ? '<td style="padding:2px 2px;"><a hidefocus title="' +
            COLORS[i] +
            '" onclick="return false;" href="javascript:" unselectable="on" class="edui-box edui-colorpicker-colorcell"' +
            ' data-color="#' +
            COLORS[i] +
            '"' +
            ' style="background-color:#' +
            COLORS[i] +
            ";border:solid #ccc 1px;" +
            '"' +
            "></a></td>"
        : "";
    }
    html += "</tr>";
    html += "</table></div>";
    return html;
  }
})();


// ui/tablepicker.js
///import core
///import uicore
(function() {
  var utils = baidu.editor.utils,
    uiUtils = baidu.editor.ui.uiUtils,
    UIBase = baidu.editor.ui.UIBase;

  var TablePicker = (baidu.editor.ui.TablePicker = function(options) {
    this.initOptions(options);
    this.initTablePicker();
  });
  TablePicker.prototype = {
    defaultNumRows: 10,
    defaultNumCols: 10,
    maxNumRows: 20,
    maxNumCols: 20,
    numRows: 10,
    numCols: 10,
    lengthOfCellSide: 22,
    initTablePicker: function() {
      this.initUIBase();
    },
    getHtmlTpl: function() {
      var me = this;
      return (
        '<div id="##" class="edui-tablepicker %%">' +
        '<div class="edui-tablepicker-body">' +
        '<div class="edui-infoarea">' +
        '<span id="##_label" class="edui-label"></span>' +
        "</div>" +
        '<div class="edui-pickarea"' +
        ' onmousemove="$$._onMouseMove(event, this);"' +
        ' onmouseover="$$._onMouseOver(event, this);"' +
        ' onmouseout="$$._onMouseOut(event, this);"' +
        ' onclick="$$._onClick(event, this);"' +
        ">" +
        '<div id="##_overlay" class="edui-overlay"></div>' +
        "</div>" +
        "</div>" +
        "</div>"
      );
    },
    _UIBase_render: UIBase.prototype.render,
    render: function(holder) {
      this._UIBase_render(holder);
      this.getDom("label").innerHTML =
        "0" +
        this.editor.getLang("t_row") +
        " x 0" +
        this.editor.getLang("t_col");
    },
    _track: function(numCols, numRows) {
      var style = this.getDom("overlay").style;
      var sideLen = this.lengthOfCellSide;
      style.width = numCols * sideLen + "px";
      style.height = numRows * sideLen + "px";
      var label = this.getDom("label");
      label.innerHTML =
        numCols +
        this.editor.getLang("t_col") +
        " x " +
        numRows +
        this.editor.getLang("t_row");
      this.numCols = numCols;
      this.numRows = numRows;
    },
    _onMouseOver: function(evt, el) {
      var rel = evt.relatedTarget || evt.fromElement;
      if (!uiUtils.contains(el, rel) && el !== rel) {
        this.getDom("label").innerHTML =
          "0" +
          this.editor.getLang("t_col") +
          " x 0" +
          this.editor.getLang("t_row");
        this.getDom("overlay").style.visibility = "";
      }
    },
    _onMouseOut: function(evt, el) {
      var rel = evt.relatedTarget || evt.toElement;
      if (!uiUtils.contains(el, rel) && el !== rel) {
        this.getDom("label").innerHTML =
          "0" +
          this.editor.getLang("t_col") +
          " x 0" +
          this.editor.getLang("t_row");
        this.getDom("overlay").style.visibility = "hidden";
      }
    },
    _onMouseMove: function(evt, el) {
      var style = this.getDom("overlay").style;
      var offset = uiUtils.getEventOffset(evt);
      var sideLen = this.lengthOfCellSide;
      var numCols = Math.ceil(offset.left / sideLen);
      var numRows = Math.ceil(offset.top / sideLen);
      this._track(numCols, numRows);
    },
    _onClick: function() {
      this.fireEvent("picktable", this.numCols, this.numRows);
    }
  };
  utils.inherits(TablePicker, UIBase);
})();


// ui/stateful.js
(function() {
  var browser = baidu.editor.browser,
    domUtils = baidu.editor.dom.domUtils,
    uiUtils = baidu.editor.ui.uiUtils;

  var TPL_STATEFUL =
    'onmousedown="$$.Stateful_onMouseDown(event, this);"' +
    ' onmouseup="$$.Stateful_onMouseUp(event, this);"' +
    (browser.ie
      ? ' onmouseenter="$$.Stateful_onMouseEnter(event, this);"' +
          ' onmouseleave="$$.Stateful_onMouseLeave(event, this);"'
      : ' onmouseover="$$.Stateful_onMouseOver(event, this);"' +
          ' onmouseout="$$.Stateful_onMouseOut(event, this);"');

  baidu.editor.ui.Stateful = {
    alwalysHoverable: false,
    target: null, //目标元素和this指向dom不一样
    Stateful_init: function() {
      this._Stateful_dGetHtmlTpl = this.getHtmlTpl;
      this.getHtmlTpl = this.Stateful_getHtmlTpl;
    },
    Stateful_getHtmlTpl: function() {
      var tpl = this._Stateful_dGetHtmlTpl();
      // 使用function避免$转义
      return tpl.replace(/stateful/g, function() {
        return TPL_STATEFUL;
      });
    },
    Stateful_onMouseEnter: function(evt, el) {
      this.target = el;
      if (!this.isDisabled() || this.alwalysHoverable) {
        this.addState("hover");
        this.fireEvent("over");
      }
    },
    Stateful_onMouseLeave: function(evt, el) {
      if (!this.isDisabled() || this.alwalysHoverable) {
        this.removeState("hover");
        this.removeState("active");
        this.fireEvent("out");
      }
    },
    Stateful_onMouseOver: function(evt, el) {
      var rel = evt.relatedTarget;
      if (!uiUtils.contains(el, rel) && el !== rel) {
        this.Stateful_onMouseEnter(evt, el);
      }
    },
    Stateful_onMouseOut: function(evt, el) {
      var rel = evt.relatedTarget;
      if (!uiUtils.contains(el, rel) && el !== rel) {
        this.Stateful_onMouseLeave(evt, el);
      }
    },
    Stateful_onMouseDown: function(evt, el) {
      if (!this.isDisabled()) {
        this.addState("active");
      }
    },
    Stateful_onMouseUp: function(evt, el) {
      if (!this.isDisabled()) {
        this.removeState("active");
      }
    },
    Stateful_postRender: function() {
      if (this.disabled && !this.hasState("disabled")) {
        this.addState("disabled");
      }
    },
    hasState: function(state) {
      return domUtils.hasClass(this.getStateDom(), "edui-state-" + state);
    },
    addState: function(state) {
      if (!this.hasState(state)) {
        this.getStateDom().className += " edui-state-" + state;
      }
    },
    removeState: function(state) {
      if (this.hasState(state)) {
        domUtils.removeClasses(this.getStateDom(), ["edui-state-" + state]);
      }
    },
    getStateDom: function() {
      return this.getDom("state");
    },
    isChecked: function() {
      return this.hasState("checked");
    },
    setChecked: function(checked) {
      if (!this.isDisabled() && checked) {
        this.addState("checked");
      } else {
        this.removeState("checked");
      }
    },
    isDisabled: function() {
      return this.hasState("disabled");
    },
    setDisabled: function(disabled) {
      if (disabled) {
        this.removeState("hover");
        this.removeState("checked");
        this.removeState("active");
        this.addState("disabled");
      } else {
        this.removeState("disabled");
      }
    }
  };
})();


// ui/button.js
///import core
///import uicore
///import ui/stateful.js
(function() {
  var utils = baidu.editor.utils,
    UIBase = baidu.editor.ui.UIBase,
    Stateful = baidu.editor.ui.Stateful,
    Button = (baidu.editor.ui.Button = function(options) {
      if (options.name) {
        var btnName = options.name;
        var cssRules = options.cssRules;
        if (!options.className) {
          options.className = "edui-for-" + btnName;
        }
        options.cssRules =
          ".edui-" +
          (options.theme || "default") +
          " .edui-toolbar .edui-button.edui-for-" +
          btnName +
          " .edui-icon {" +
          cssRules +
          "}";
      }
      this.initOptions(options);
      this.initButton();
    });
  Button.prototype = {
    uiName: "button",
    label: "",
    title: "",
    showIcon: true,
    showText: true,
    cssRules: "",
    initButton: function() {
      this.initUIBase();
      this.Stateful_init();
      if (this.cssRules) {
        utils.cssRule("edui-customize-" + this.name + "-style", this.cssRules);
      }
    },
    getHtmlTpl: function() {
      return (
        '<div id="##" class="edui-box %%">' +
        '<div id="##_state" stateful>' +
        '<div class="%%-wrap"><div id="##_body" unselectable="on" ' +
        (this.title ? 'title="' + this.title + '"' : "") +
        ' class="%%-body" onmousedown="return $$._onMouseDown(event, this);" onclick="return $$._onClick(event, this);">' +
        (this.showIcon ? '<div class="edui-box edui-icon"></div>' : "") +
        (this.showText
          ? '<div class="edui-box edui-label">' + this.label + "</div>"
          : "") +
        "</div>" +
        "</div>" +
        "</div></div>"
      );
    },
    postRender: function() {
      this.Stateful_postRender();
      this.setDisabled(this.disabled);
    },
    _onMouseDown: function(e) {
      var target = e.target || e.srcElement,
        tagName = target && target.tagName && target.tagName.toLowerCase();
      if (tagName == "input" || tagName == "object" || tagName == "object") {
        return false;
      }
    },
    _onClick: function() {
      if (!this.isDisabled()) {
        this.fireEvent("click");
      }
    },
    setTitle: function(text) {
      var label = this.getDom("label");
      label.innerHTML = text;
    }
  };
  utils.inherits(Button, UIBase);
  utils.extend(Button.prototype, Stateful);
})();


// ui/splitbutton.js
///import core
///import uicore
///import ui/stateful.js
(function() {
  var utils = baidu.editor.utils,
    uiUtils = baidu.editor.ui.uiUtils,
    domUtils = baidu.editor.dom.domUtils,
    UIBase = baidu.editor.ui.UIBase,
    Stateful = baidu.editor.ui.Stateful,
    SplitButton = (baidu.editor.ui.SplitButton = function(options) {
      this.initOptions(options);
      this.initSplitButton();
    });
  SplitButton.prototype = {
    popup: null,
    uiName: "splitbutton",
    title: "",
    initSplitButton: function() {
      this.initUIBase();
      this.Stateful_init();
      var me = this;
      if (this.popup != null) {
        var popup = this.popup;
        this.popup = null;
        this.setPopup(popup);
      }
    },
    _UIBase_postRender: UIBase.prototype.postRender,
    postRender: function() {
      this.Stateful_postRender();
      this._UIBase_postRender();
    },
    setPopup: function(popup) {
      if (this.popup === popup) return;
      if (this.popup != null) {
        this.popup.dispose();
      }
      popup.addListener("show", utils.bind(this._onPopupShow, this));
      popup.addListener("hide", utils.bind(this._onPopupHide, this));
      popup.addListener(
        "postrender",
        utils.bind(function() {
          popup
            .getDom("body")
            .appendChild(
              uiUtils.createElementByHtml(
                '<div id="' +
                  this.popup.id +
                  '_bordereraser" class="edui-bordereraser edui-background" style="width:' +
                  (uiUtils.getClientRect(this.getDom()).width + 20) +
                  'px"></div>'
              )
            );
          popup.getDom().className += " " + this.className;
        }, this)
      );
      this.popup = popup;
    },
    _onPopupShow: function() {
      this.addState("opened");
    },
    _onPopupHide: function() {
      this.removeState("opened");
    },
    getHtmlTpl: function() {
      return (
        '<div id="##" class="edui-box %%">' +
        "<div " +
        (this.title ? 'title="' + this.title + '"' : "") +
        ' id="##_state" stateful><div class="%%-body">' +
        '<div id="##_button_body" class="edui-box edui-button-body" onclick="$$._onButtonClick(event, this);">' +
        '<div class="edui-box edui-icon"></div>' +
        "</div>" +
        '<div class="edui-box edui-splitborder"></div>' +
        '<div class="edui-box edui-arrow" onclick="$$._onArrowClick();"></div>' +
        "</div></div></div>"
      );
    },
    showPopup: function() {
      // 当popup往上弹出的时候，做特殊处理
      var rect = uiUtils.getClientRect(this.getDom());
      rect.top -= this.popup.SHADOW_RADIUS;
      rect.height += this.popup.SHADOW_RADIUS;
      this.popup.showAnchorRect(rect);
    },
    _onArrowClick: function(event, el) {
      if (!this.isDisabled()) {
        this.showPopup();
      }
    },
    _onButtonClick: function() {
      if (!this.isDisabled()) {
        this.fireEvent("buttonclick");
      }
    }
  };
  utils.inherits(SplitButton, UIBase);
  utils.extend(SplitButton.prototype, Stateful, true);
})();


// ui/colorbutton.js
///import core
///import uicore
///import ui/colorpicker.js
///import ui/popup.js
///import ui/splitbutton.js
(function() {
  var utils = baidu.editor.utils,
    uiUtils = baidu.editor.ui.uiUtils,
    ColorPicker = baidu.editor.ui.ColorPicker,
    Popup = baidu.editor.ui.Popup,
    SplitButton = baidu.editor.ui.SplitButton,
    ColorButton = (baidu.editor.ui.ColorButton = function(options) {
      this.initOptions(options);
      this.initColorButton();
    });
  ColorButton.prototype = {
    initColorButton: function() {
      var me = this;
      this.popup = new Popup({
        content: new ColorPicker({
          noColorText: me.editor.getLang("clearColor"),
          editor: me.editor,
          onpickcolor: function(t, color) {
            me._onPickColor(color);
          },
          onpicknocolor: function(t, color) {
            me._onPickNoColor(color);
          }
        }),
        editor: me.editor
      });
      this.initSplitButton();
    },
    _SplitButton_postRender: SplitButton.prototype.postRender,
    postRender: function() {
      this._SplitButton_postRender();
      this.getDom("button_body").appendChild(
        uiUtils.createElementByHtml(
          '<div id="' + this.id + '_colorlump" class="edui-colorlump"></div>'
        )
      );
      this.getDom().className += " edui-colorbutton";
    },
    setColor: function(color) {
      this.getDom("colorlump").style.backgroundColor = color;
      this.color = color;
    },
    _onPickColor: function(color) {
      if (this.fireEvent("pickcolor", color) !== false) {
        this.setColor(color);
        this.popup.hide();
      }
    },
    _onPickNoColor: function(color) {
      if (this.fireEvent("picknocolor") !== false) {
        this.popup.hide();
      }
    }
  };
  utils.inherits(ColorButton, SplitButton);
})();


// ui/tablebutton.js
///import core
///import uicore
///import ui/popup.js
///import ui/tablepicker.js
///import ui/splitbutton.js
(function() {
  var utils = baidu.editor.utils,
    Popup = baidu.editor.ui.Popup,
    TablePicker = baidu.editor.ui.TablePicker,
    SplitButton = baidu.editor.ui.SplitButton,
    TableButton = (baidu.editor.ui.TableButton = function(options) {
      this.initOptions(options);
      this.initTableButton();
    });
  TableButton.prototype = {
    initTableButton: function() {
      var me = this;
      this.popup = new Popup({
        content: new TablePicker({
          editor: me.editor,
          onpicktable: function(t, numCols, numRows) {
            me._onPickTable(numCols, numRows);
          }
        }),
        editor: me.editor
      });
      this.initSplitButton();
    },
    _onPickTable: function(numCols, numRows) {
      if (this.fireEvent("picktable", numCols, numRows) !== false) {
        this.popup.hide();
      }
    }
  };
  utils.inherits(TableButton, SplitButton);
})();


// ui/autotypesetpicker.js
///import core
///import uicore
(function() {
  var utils = baidu.editor.utils,
    UIBase = baidu.editor.ui.UIBase;

  var AutoTypeSetPicker = (baidu.editor.ui.AutoTypeSetPicker = function(
    options
  ) {
    this.initOptions(options);
    this.initAutoTypeSetPicker();
  });
  AutoTypeSetPicker.prototype = {
    initAutoTypeSetPicker: function() {
      this.initUIBase();
    },
    getHtmlTpl: function() {
      var me = this.editor,
        opt = me.options.autotypeset,
        lang = me.getLang("autoTypeSet");

      var textAlignInputName = "textAlignValue" + me.uid,
        imageBlockInputName = "imageBlockLineValue" + me.uid,
        symbolConverInputName = "symbolConverValue" + me.uid;

      return (
        '<div id="##" class="edui-autotypesetpicker %%">' +
        '<div class="edui-autotypesetpicker-body">' +
        "<table >" +
        '<tr><td nowrap><input type="checkbox" name="mergeEmptyline" ' +
        (opt["mergeEmptyline"] ? "checked" : "") +
        ">" +
        lang.mergeLine +
        '</td><td colspan="2"><input type="checkbox" name="removeEmptyline" ' +
        (opt["removeEmptyline"] ? "checked" : "") +
        ">" +
        lang.delLine +
        "</td></tr>" +
        '<tr><td nowrap><input type="checkbox" name="removeClass" ' +
        (opt["removeClass"] ? "checked" : "") +
        ">" +
        lang.removeFormat +
        '</td><td colspan="2"><input type="checkbox" name="indent" ' +
        (opt["indent"] ? "checked" : "") +
        ">" +
        lang.indent +
        "</td></tr>" +
        "<tr>" +
        '<td nowrap><input type="checkbox" name="textAlign" ' +
        (opt["textAlign"] ? "checked" : "") +
        ">" +
        lang.alignment +
        "</td>" +
        '<td colspan="2" id="' +
        textAlignInputName +
        '">' +
        '<input type="radio" name="' +
        textAlignInputName +
        '" value="left" ' +
        (opt["textAlign"] && opt["textAlign"] == "left" ? "checked" : "") +
        ">" +
        me.getLang("justifyleft") +
        '<input type="radio" name="' +
        textAlignInputName +
        '" value="center" ' +
        (opt["textAlign"] && opt["textAlign"] == "center" ? "checked" : "") +
        ">" +
        me.getLang("justifycenter") +
        '<input type="radio" name="' +
        textAlignInputName +
        '" value="right" ' +
        (opt["textAlign"] && opt["textAlign"] == "right" ? "checked" : "") +
        ">" +
        me.getLang("justifyright") +
        "</td>" +
        "</tr>" +
        "<tr>" +
        '<td nowrap><input type="checkbox" name="imageBlockLine" ' +
        (opt["imageBlockLine"] ? "checked" : "") +
        ">" +
        lang.imageFloat +
        "</td>" +
        '<td nowrap id="' +
        imageBlockInputName +
        '">' +
        '<input type="radio" name="' +
        imageBlockInputName +
        '" value="none" ' +
        (opt["imageBlockLine"] && opt["imageBlockLine"] == "none"
          ? "checked"
          : "") +
        ">" +
        me.getLang("default") +
        '<input type="radio" name="' +
        imageBlockInputName +
        '" value="left" ' +
        (opt["imageBlockLine"] && opt["imageBlockLine"] == "left"
          ? "checked"
          : "") +
        ">" +
        me.getLang("justifyleft") +
        '<input type="radio" name="' +
        imageBlockInputName +
        '" value="center" ' +
        (opt["imageBlockLine"] && opt["imageBlockLine"] == "center"
          ? "checked"
          : "") +
        ">" +
        me.getLang("justifycenter") +
        '<input type="radio" name="' +
        imageBlockInputName +
        '" value="right" ' +
        (opt["imageBlockLine"] && opt["imageBlockLine"] == "right"
          ? "checked"
          : "") +
        ">" +
        me.getLang("justifyright") +
        "</td>" +
        "</tr>" +
        '<tr><td nowrap><input type="checkbox" name="clearFontSize" ' +
        (opt["clearFontSize"] ? "checked" : "") +
        ">" +
        lang.removeFontsize +
        '</td><td colspan="2"><input type="checkbox" name="clearFontFamily" ' +
        (opt["clearFontFamily"] ? "checked" : "") +
        ">" +
        lang.removeFontFamily +
        "</td></tr>" +
        '<tr><td nowrap colspan="3"><input type="checkbox" name="removeEmptyNode" ' +
        (opt["removeEmptyNode"] ? "checked" : "") +
        ">" +
        lang.removeHtml +
        "</td></tr>" +
        '<tr><td nowrap colspan="3"><input type="checkbox" name="pasteFilter" ' +
        (opt["pasteFilter"] ? "checked" : "") +
        ">" +
        lang.pasteFilter +
        "</td></tr>" +
        "<tr>" +
        '<td nowrap><input type="checkbox" name="symbolConver" ' +
        (opt["bdc2sb"] || opt["tobdc"] ? "checked" : "") +
        ">" +
        lang.symbol +
        "</td>" +
        '<td id="' +
        symbolConverInputName +
        '">' +
        '<input type="radio" name="bdc" value="bdc2sb" ' +
        (opt["bdc2sb"] ? "checked" : "") +
        ">" +
        lang.bdc2sb +
        '<input type="radio" name="bdc" value="tobdc" ' +
        (opt["tobdc"] ? "checked" : "") +
        ">" +
        lang.tobdc +
        "" +
        "</td>" +
        '<td nowrap align="right"><button >' +
        lang.run +
        "</button></td>" +
        "</tr>" +
        "</table>" +
        "</div>" +
        "</div>"
      );
    },
    _UIBase_render: UIBase.prototype.render
  };
  utils.inherits(AutoTypeSetPicker, UIBase);
})();


// ui/autotypesetbutton.js
///import core
///import uicore
///import ui/popup.js
///import ui/autotypesetpicker.js
///import ui/splitbutton.js
(function() {
  var utils = baidu.editor.utils,
    Popup = baidu.editor.ui.Popup,
    AutoTypeSetPicker = baidu.editor.ui.AutoTypeSetPicker,
    SplitButton = baidu.editor.ui.SplitButton,
    AutoTypeSetButton = (baidu.editor.ui.AutoTypeSetButton = function(options) {
      this.initOptions(options);
      this.initAutoTypeSetButton();
    });
  function getPara(me) {
    var opt = {},
      cont = me.getDom(),
      editorId = me.editor.uid,
      inputType = null,
      attrName = null,
      ipts = domUtils.getElementsByTagName(cont, "input");
    for (var i = ipts.length - 1, ipt; (ipt = ipts[i--]); ) {
      inputType = ipt.getAttribute("type");
      if (inputType == "checkbox") {
        attrName = ipt.getAttribute("name");
        opt[attrName] && delete opt[attrName];
        if (ipt.checked) {
          var attrValue = document.getElementById(
            attrName + "Value" + editorId
          );
          if (attrValue) {
            if (/input/gi.test(attrValue.tagName)) {
              opt[attrName] = attrValue.value;
            } else {
              var iptChilds = attrValue.getElementsByTagName("input");
              for (
                var j = iptChilds.length - 1, iptchild;
                (iptchild = iptChilds[j--]);

              ) {
                if (iptchild.checked) {
                  opt[attrName] = iptchild.value;
                  break;
                }
              }
            }
          } else {
            opt[attrName] = true;
          }
        } else {
          opt[attrName] = false;
        }
      } else {
        opt[ipt.getAttribute("value")] = ipt.checked;
      }
    }

    var selects = domUtils.getElementsByTagName(cont, "select");
    for (var i = 0, si; (si = selects[i++]); ) {
      var attr = si.getAttribute("name");
      opt[attr] = opt[attr] ? si.value : "";
    }

    utils.extend(me.editor.options.autotypeset, opt);

    me.editor.setPreferences("autotypeset", opt);
  }

  AutoTypeSetButton.prototype = {
    initAutoTypeSetButton: function() {
      var me = this;
      this.popup = new Popup({
        //传入配置参数
        content: new AutoTypeSetPicker({ editor: me.editor }),
        editor: me.editor,
        hide: function() {
          if (!this._hidden && this.getDom()) {
            getPara(this);
            this.getDom().style.display = "none";
            this._hidden = true;
            this.fireEvent("hide");
          }
        }
      });
      var flag = 0;
      this.popup.addListener("postRenderAfter", function() {
        var popupUI = this;
        if (flag) return;
        var cont = this.getDom(),
          btn = cont.getElementsByTagName("button")[0];

        btn.onclick = function() {
          getPara(popupUI);
          me.editor.execCommand("autotypeset");
          popupUI.hide();
        };

        domUtils.on(cont, "click", function(e) {
          var target = e.target || e.srcElement,
            editorId = me.editor.uid;
          if (target && target.tagName == "INPUT") {
            // 点击图片浮动的checkbox,去除对应的radio
            if (
              target.name == "imageBlockLine" ||
              target.name == "textAlign" ||
              target.name == "symbolConver"
            ) {
              var checked = target.checked,
                radioTd = document.getElementById(
                  target.name + "Value" + editorId
                ),
                radios = radioTd.getElementsByTagName("input"),
                defalutSelect = {
                  imageBlockLine: "none",
                  textAlign: "left",
                  symbolConver: "tobdc"
                };

              for (var i = 0; i < radios.length; i++) {
                if (checked) {
                  if (radios[i].value == defalutSelect[target.name]) {
                    radios[i].checked = "checked";
                  }
                } else {
                  radios[i].checked = false;
                }
              }
            }
            // 点击radio,选中对应的checkbox
            if (
              target.name == "imageBlockLineValue" + editorId ||
              target.name == "textAlignValue" + editorId ||
              target.name == "bdc"
            ) {
              var checkboxs = target.parentNode.previousSibling.getElementsByTagName(
                "input"
              );
              checkboxs && (checkboxs[0].checked = true);
            }

            getPara(popupUI);
          }
        });

        flag = 1;
      });
      this.initSplitButton();
    }
  };
  utils.inherits(AutoTypeSetButton, SplitButton);
})();


// ui/cellalignpicker.js
///import core
///import uicore
(function() {
  var utils = baidu.editor.utils,
    Popup = baidu.editor.ui.Popup,
    Stateful = baidu.editor.ui.Stateful,
    UIBase = baidu.editor.ui.UIBase;

  /**
     * 该参数将新增一个参数： selected， 参数类型为一个Object， 形如{ 'align': 'center', 'valign': 'top' }， 表示单元格的初始
     * 对齐状态为： 竖直居上，水平居中; 其中 align的取值为：'center', 'left', 'right'; valign的取值为: 'top', 'middle', 'bottom'
     * @update 2013/4/2 hancong03@baidu.com
     */
  var CellAlignPicker = (baidu.editor.ui.CellAlignPicker = function(options) {
    this.initOptions(options);
    this.initSelected();
    this.initCellAlignPicker();
  });
  CellAlignPicker.prototype = {
    //初始化选中状态， 该方法将根据传递进来的参数获取到应该选中的对齐方式图标的索引
    initSelected: function() {
      var status = {
        valign: {
          top: 0,
          middle: 1,
          bottom: 2
        },
        align: {
          left: 0,
          center: 1,
          right: 2
        },
        count: 3
      },
        result = -1;

      if (this.selected) {
        this.selectedIndex =
          status.valign[this.selected.valign] * status.count +
          status.align[this.selected.align];
      }
    },
    initCellAlignPicker: function() {
      this.initUIBase();
      this.Stateful_init();
    },
    getHtmlTpl: function() {
      var alignType = ["left", "center", "right"],
        COUNT = 9,
        tempClassName = null,
        tempIndex = -1,
        tmpl = [];

      for (var i = 0; i < COUNT; i++) {
        tempClassName = this.selectedIndex === i
          ? ' class="edui-cellalign-selected" '
          : "";
        tempIndex = i % 3;

        tempIndex === 0 && tmpl.push("<tr>");

        tmpl.push(
          '<td index="' +
            i +
            '" ' +
            tempClassName +
            ' stateful><div class="edui-icon edui-' +
            alignType[tempIndex] +
            '"></div></td>'
        );

        tempIndex === 2 && tmpl.push("</tr>");
      }

      return (
        '<div id="##" class="edui-cellalignpicker %%">' +
        '<div class="edui-cellalignpicker-body">' +
        '<table onclick="$$._onClick(event);">' +
        tmpl.join("") +
        "</table>" +
        "</div>" +
        "</div>"
      );
    },
    getStateDom: function() {
      return this.target;
    },
    _onClick: function(evt) {
      var target = evt.target || evt.srcElement;
      if (/icon/.test(target.className)) {
        this.items[target.parentNode.getAttribute("index")].onclick();
        Popup.postHide(evt);
      }
    },
    _UIBase_render: UIBase.prototype.render
  };
  utils.inherits(CellAlignPicker, UIBase);
  utils.extend(CellAlignPicker.prototype, Stateful, true);
})();


// ui/pastepicker.js
///import core
///import uicore
(function() {
  var utils = baidu.editor.utils,
    Stateful = baidu.editor.ui.Stateful,
    uiUtils = baidu.editor.ui.uiUtils,
    UIBase = baidu.editor.ui.UIBase;

  var PastePicker = (baidu.editor.ui.PastePicker = function(options) {
    this.initOptions(options);
    this.initPastePicker();
  });
  PastePicker.prototype = {
    initPastePicker: function() {
      this.initUIBase();
      this.Stateful_init();
    },
    getHtmlTpl: function() {
      return (
        '<div class="edui-pasteicon" onclick="$$._onClick(this)"></div>' +
        '<div class="edui-pastecontainer">' +
        '<div class="edui-title">' +
        this.editor.getLang("pasteOpt") +
        "</div>" +
        '<div class="edui-button">' +
        '<div title="' +
        this.editor.getLang("pasteSourceFormat") +
        '" onclick="$$.format(false)" stateful>' +
        '<div class="edui-richtxticon"></div></div>' +
        '<div title="' +
        this.editor.getLang("tagFormat") +
        '" onclick="$$.format(2)" stateful>' +
        '<div class="edui-tagicon"></div></div>' +
        '<div title="' +
        this.editor.getLang("pasteTextFormat") +
        '" onclick="$$.format(true)" stateful>' +
        '<div class="edui-plaintxticon"></div></div>' +
        "</div>" +
        "</div>" +
        "</div>"
      );
    },
    getStateDom: function() {
      return this.target;
    },
    format: function(param) {
      this.editor.ui._isTransfer = true;
      this.editor.fireEvent("pasteTransfer", param);
    },
    _onClick: function(cur) {
      var node = domUtils.getNextDomNode(cur),
        screenHt = uiUtils.getViewportRect().height,
        subPop = uiUtils.getClientRect(node);

      if (subPop.top + subPop.height > screenHt)
        node.style.top = -subPop.height - cur.offsetHeight + "px";
      else node.style.top = "";

      if (/hidden/gi.test(domUtils.getComputedStyle(node, "visibility"))) {
        node.style.visibility = "visible";
        domUtils.addClass(cur, "edui-state-opened");
      } else {
        node.style.visibility = "hidden";
        domUtils.removeClasses(cur, "edui-state-opened");
      }
    },
    _UIBase_render: UIBase.prototype.render
  };
  utils.inherits(PastePicker, UIBase);
  utils.extend(PastePicker.prototype, Stateful, true);
})();


// ui/toolbar.js
(function() {
  var utils = baidu.editor.utils,
    uiUtils = baidu.editor.ui.uiUtils,
    UIBase = baidu.editor.ui.UIBase,
    Toolbar = (baidu.editor.ui.Toolbar = function(options) {
      this.initOptions(options);
      this.initToolbar();
    });
  Toolbar.prototype = {
    items: null,
    initToolbar: function() {
      this.items = this.items || [];
      this.initUIBase();
    },
    add: function(item, index) {
      if (index === undefined) {
        this.items.push(item);
      } else {
        this.items.splice(index, 0, item);
      }
    },
    getHtmlTpl: function() {
      var buff = [];
      for (var i = 0; i < this.items.length; i++) {
        buff[i] = this.items[i].renderHtml();
      }
      return (
        '<div id="##" class="edui-toolbar %%" onselectstart="return false;" onmousedown="return $$._onMouseDown(event, this);">' +
        buff.join("") +
        "</div>"
      );
    },
    postRender: function() {
      var box = this.getDom();
      for (var i = 0; i < this.items.length; i++) {
        this.items[i].postRender();
      }
      uiUtils.makeUnselectable(box);
    },
    _onMouseDown: function(e) {
      var target = e.target || e.srcElement,
        tagName = target && target.tagName && target.tagName.toLowerCase();
      if (tagName == "input" || tagName == "object" || tagName == "object") {
        return false;
      }
    }
  };
  utils.inherits(Toolbar, UIBase);
})();


// ui/quick-operate.js
///import core
///import uicore
///import ui\popup.js
///import ui\stateful.js
(function () {
  var utils = baidu.editor.utils,
    domUtils = baidu.editor.dom.domUtils,
    uiUtils = baidu.editor.ui.uiUtils,
    UIBase = baidu.editor.ui.UIBase,
    Popup = baidu.editor.ui.Popup,
    Stateful = baidu.editor.ui.Stateful,
    CellAlignPicker = baidu.editor.ui.CellAlignPicker,
    QuickOperate = (baidu.editor.ui.QuickOperate = function (options) {
      this.initOptions(options);
      // this.initMenu();
    });

  // var menuSeparator = {
  //   renderHtml: function() {
  //     return '<div class="edui-menuitem edui-menuseparator"><div class="edui-menuseparator-inner"></div></div>';
  //   },
  //   postRender: function() {},
  //   queryAutoHide: function() {
  //     return true;
  //   }
  // };
  QuickOperate.prototype = {
    //   items: null,
    uiName: "quick-operate",
    //   initMenu: function() {
    //     this.items = this.items || [];
    //     this.initPopup();
    //     this.initItems();
    //   },
    //   initItems: function() {
    //     for (var i = 0; i < this.items.length; i++) {
    //       var item = this.items[i];
    //       if (item == "-") {
    //         this.items[i] = this.getSeparator();
    //       } else if (!(item instanceof MenuItem)) {
    //         item.editor = this.editor;
    //         item.theme = this.editor.options.theme;
    //         this.items[i] = this.createItem(item);
    //       }
    //     }
    //   },
    //   getSeparator: function() {
    //     return menuSeparator;
    //   },
    //   createItem: function(item) {
    //     //新增一个参数menu, 该参数存储了menuItem所对应的menu引用
    //     item.menu = this;
    //     return new MenuItem(item);
    //   },
    _Popup_getContentHtmlTpl: Popup.prototype.getContentHtmlTpl,
    getContentHtmlTpl: function () {
      //     if (this.items.length == 0) {
      //       return this._Popup_getContentHtmlTpl();
      //     }
      //     var buff = [];
      //     for (var i = 0; i < this.items.length; i++) {
      //       var item = this.items[i];
      //       buff[i] = item.renderHtml();
      //     }
      //     return '<div class="%%-body">' + buff.join("") + "</div>";
      return [
        '<div class="edui-quick-operate">',
        ' <div class="edui-quick-operate-status">',
        '   <div class="edui-quick-operate-icon"><i class="icon icon-image"></i></div>',
        '   <div class="edui-quick-operate-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" data-icon="DragOutlined"><path d="M8.25 6.5a1.75 1.75 0 1 0 0-3.5 1.75 1.75 0 0 0 0 3.5Zm0 7.25a1.75 1.75 0 1 0 0-3.5 1.75 1.75 0 0 0 0 3.5Zm1.75 5.5a1.75 1.75 0 1 1-3.5 0 1.75 1.75 0 0 1 3.5 0ZM14.753 6.5a1.75 1.75 0 1 0 0-3.5 1.75 1.75 0 0 0 0 3.5ZM16.5 12a1.75 1.75 0 1 1-3.5 0 1.75 1.75 0 0 1 3.5 0Zm-1.747 9a1.75 1.75 0 1 0 0-3.5 1.75 1.75 0 0 0 0 3.5Z" fill="currentColor"></path></svg></div>',
        ' </div>',
        ' <div class="edui-quick-operate-menu">',
        '   <div class="item"><i class="icon icon-image"></i> 删除</div>',
        '   <div class="item"><i class="icon icon-image"></i> 左对齐</div>',
        '   <div class="item"><i class="icon icon-image"></i> 右对齐</div>',
        ' </div>',
        '</div>',
      ].join('')
    },
    //   _Popup_postRender: Popup.prototype.postRender,
    //   postRender: function() {
    //     var me = this;
    //     for (var i = 0; i < this.items.length; i++) {
    //       var item = this.items[i];
    //       item.ownerMenu = this;
    //       item.postRender();
    //     }
    //     domUtils.on(this.getDom(), "mouseover", function(evt) {
    //       evt = evt || event;
    //       var rel = evt.relatedTarget || evt.fromElement;
    //       var el = me.getDom();
    //       if (!uiUtils.contains(el, rel) && el !== rel) {
    //         me.fireEvent("over");
    //       }
    //     });
    //     this._Popup_postRender();
    //   },
    //   queryAutoHide: function(el) {
    //     if (el) {
    //       if (uiUtils.contains(this.getDom(), el)) {
    //         return false;
    //       }
    //       for (var i = 0; i < this.items.length; i++) {
    //         var item = this.items[i];
    //         if (item.queryAutoHide(el) === false) {
    //           return false;
    //         }
    //       }
    //     }
    //   },
    //   clearItems: function() {
    //     for (var i = 0; i < this.items.length; i++) {
    //       var item = this.items[i];
    //       clearTimeout(item._showingTimer);
    //       clearTimeout(item._closingTimer);
    //       if (item.subMenu) {
    //         item.subMenu.destroy();
    //       }
    //     }
    //     this.items = [];
    //   },
    destroy: function () {
      if (this.getDom()) {
        domUtils.remove(this.getDom());
      }
      //     this.clearItems();
    },
    dispose: function () {
      this.destroy();
    }
  };
  utils.inherits(QuickOperate, Popup);
  //
  // /**
  //    * @update 2013/04/03 hancong03 新增一个参数menu, 该参数存储了menuItem所对应的menu引用
  //    * @type {Function}
  //    */
  // var MenuItem = (baidu.editor.ui.MenuItem = function(options) {
  //   this.initOptions(options);
  //   this.initUIBase();
  //   this.Stateful_init();
  //   if (this.subMenu && !(this.subMenu instanceof QuickOperate)) {
  //     if (options.className && options.className.indexOf("aligntd") != -1) {
  //       var me = this;
  //
  //       //获取单元格对齐初始状态
  //       this.subMenu.selected = this.editor.queryCommandValue("cellalignment");
  //
  //       this.subMenu = new Popup({
  //         content: new CellAlignPicker(this.subMenu),
  //         parentMenu: me,
  //         editor: me.editor,
  //         destroy: function() {
  //           if (this.getDom()) {
  //             domUtils.remove(this.getDom());
  //           }
  //         }
  //       });
  //       this.subMenu.addListener("postRenderAfter", function() {
  //         domUtils.on(this.getDom(), "mouseover", function() {
  //           me.addState("opened");
  //         });
  //       });
  //     } else {
  //       this.subMenu = new QuickOperate(this.subMenu);
  //     }
  //   }
  // });
  // MenuItem.prototype = {
  //   label: "",
  //   subMenu: null,
  //   ownerMenu: null,
  //   uiName: "menuitem",
  //   alwalysHoverable: true,
  //   getHtmlTpl: function() {
  //     return (
  //       '<div id="##" class="%%" stateful onclick="$$._onClick(event, this);">' +
  //       '<div class="%%-body">' +
  //       this.renderLabelHtml() +
  //       "</div>" +
  //       "</div>"
  //     );
  //   },
  //   postRender: function() {
  //     var me = this;
  //     this.addListener("over", function() {
  //       me.ownerMenu.fireEvent("submenuover", me);
  //       if (me.subMenu) {
  //         me.delayShowSubMenu();
  //       }
  //     });
  //     if (this.subMenu) {
  //       this.getDom().className += " edui-hassubmenu";
  //       this.subMenu.render();
  //       this.addListener("out", function() {
  //         me.delayHideSubMenu();
  //       });
  //       this.subMenu.addListener("over", function() {
  //         clearTimeout(me._closingTimer);
  //         me._closingTimer = null;
  //         me.addState("opened");
  //       });
  //       this.ownerMenu.addListener("hide", function() {
  //         me.hideSubMenu();
  //       });
  //       this.ownerMenu.addListener("submenuover", function(t, subMenu) {
  //         if (subMenu !== me) {
  //           me.delayHideSubMenu();
  //         }
  //       });
  //       this.subMenu._bakQueryAutoHide = this.subMenu.queryAutoHide;
  //       this.subMenu.queryAutoHide = function(el) {
  //         if (el && uiUtils.contains(me.getDom(), el)) {
  //           return false;
  //         }
  //         return this._bakQueryAutoHide(el);
  //       };
  //     }
  //     this.getDom().style.tabIndex = "-1";
  //     uiUtils.makeUnselectable(this.getDom());
  //     this.Stateful_postRender();
  //   },
  //   delayShowSubMenu: function() {
  //     var me = this;
  //     if (!me.isDisabled()) {
  //       me.addState("opened");
  //       clearTimeout(me._showingTimer);
  //       clearTimeout(me._closingTimer);
  //       me._closingTimer = null;
  //       me._showingTimer = setTimeout(function() {
  //         me.showSubMenu();
  //       }, 250);
  //     }
  //   },
  //   delayHideSubMenu: function() {
  //     var me = this;
  //     if (!me.isDisabled()) {
  //       me.removeState("opened");
  //       clearTimeout(me._showingTimer);
  //       if (!me._closingTimer) {
  //         me._closingTimer = setTimeout(function() {
  //           if (!me.hasState("opened")) {
  //             me.hideSubMenu();
  //           }
  //           me._closingTimer = null;
  //         }, 400);
  //       }
  //     }
  //   },
  //   renderLabelHtml: function() {
  //     return (
  //       '<div class="edui-arrow"></div>' +
  //       '<div class="edui-box edui-icon"></div>' +
  //       '<div class="edui-box edui-label %%-label">' +
  //       (this.label || "") +
  //       "</div>"
  //     );
  //   },
  //   getStateDom: function() {
  //     return this.getDom();
  //   },
  //   queryAutoHide: function(el) {
  //     if (this.subMenu && this.hasState("opened")) {
  //       return this.subMenu.queryAutoHide(el);
  //     }
  //   },
  //   _onClick: function(event, this_) {
  //     if (this.hasState("disabled")) return;
  //     if (this.fireEvent("click", event, this_) !== false) {
  //       if (this.subMenu) {
  //         this.showSubMenu();
  //       } else {
  //         Popup.postHide(event);
  //       }
  //     }
  //   },
  //   showSubMenu: function() {
  //     var rect = uiUtils.getClientRect(this.getDom());
  //     rect.right -= 5;
  //     rect.left += 2;
  //     rect.width -= 7;
  //     rect.top -= 4;
  //     rect.bottom += 4;
  //     rect.height += 8;
  //     this.subMenu.showAnchorRect(rect, true, true);
  //   },
  //   hideSubMenu: function() {
  //     this.subMenu.hide();
  //   }
  // };
  // utils.inherits(MenuItem, UIBase);
  // utils.extend(MenuItem.prototype, Stateful, true);
})();


// ui/menu.js
///import core
///import uicore
///import ui\popup.js
///import ui\stateful.js
(function() {
  var utils = baidu.editor.utils,
    domUtils = baidu.editor.dom.domUtils,
    uiUtils = baidu.editor.ui.uiUtils,
    UIBase = baidu.editor.ui.UIBase,
    Popup = baidu.editor.ui.Popup,
    Stateful = baidu.editor.ui.Stateful,
    CellAlignPicker = baidu.editor.ui.CellAlignPicker,
    Menu = (baidu.editor.ui.Menu = function(options) {
      this.initOptions(options);
      this.initMenu();
    });

  var menuSeparator = {
    renderHtml: function() {
      return '<div class="edui-menuitem edui-menuseparator"><div class="edui-menuseparator-inner"></div></div>';
    },
    postRender: function() {},
    queryAutoHide: function() {
      return true;
    }
  };
  Menu.prototype = {
    items: null,
    uiName: "menu",
    initMenu: function() {
      this.items = this.items || [];
      this.initPopup();
      this.initItems();
    },
    initItems: function() {
      for (var i = 0; i < this.items.length; i++) {
        var item = this.items[i];
        if (item == "-") {
          this.items[i] = this.getSeparator();
        } else if (!(item instanceof MenuItem)) {
          item.editor = this.editor;
          item.theme = this.editor.options.theme;
          this.items[i] = this.createItem(item);
        }
      }
    },
    getSeparator: function() {
      return menuSeparator;
    },
    createItem: function(item) {
      //新增一个参数menu, 该参数存储了menuItem所对应的menu引用
      item.menu = this;
      return new MenuItem(item);
    },
    _Popup_getContentHtmlTpl: Popup.prototype.getContentHtmlTpl,
    getContentHtmlTpl: function() {
      if (this.items.length == 0) {
        return this._Popup_getContentHtmlTpl();
      }
      var buff = [];
      for (var i = 0; i < this.items.length; i++) {
        var item = this.items[i];
        buff[i] = item.renderHtml();
      }
      return '<div class="%%-body">' + buff.join("") + "</div>";
    },
    _Popup_postRender: Popup.prototype.postRender,
    postRender: function() {
      var me = this;
      for (var i = 0; i < this.items.length; i++) {
        var item = this.items[i];
        item.ownerMenu = this;
        item.postRender();
      }
      domUtils.on(this.getDom(), "mouseover", function(evt) {
        evt = evt || event;
        var rel = evt.relatedTarget || evt.fromElement;
        var el = me.getDom();
        if (!uiUtils.contains(el, rel) && el !== rel) {
          me.fireEvent("over");
        }
      });
      this._Popup_postRender();
    },
    queryAutoHide: function(el) {
      if (el) {
        if (uiUtils.contains(this.getDom(), el)) {
          return false;
        }
        for (var i = 0; i < this.items.length; i++) {
          var item = this.items[i];
          if (item.queryAutoHide(el) === false) {
            return false;
          }
        }
      }
    },
    clearItems: function() {
      for (var i = 0; i < this.items.length; i++) {
        var item = this.items[i];
        clearTimeout(item._showingTimer);
        clearTimeout(item._closingTimer);
        if (item.subMenu) {
          item.subMenu.destroy();
        }
      }
      this.items = [];
    },
    destroy: function() {
      if (this.getDom()) {
        domUtils.remove(this.getDom());
      }
      this.clearItems();
    },
    dispose: function() {
      this.destroy();
    }
  };
  utils.inherits(Menu, Popup);

  /**
     * @update 2013/04/03 hancong03 新增一个参数menu, 该参数存储了menuItem所对应的menu引用
     * @type {Function}
     */
  var MenuItem = (baidu.editor.ui.MenuItem = function(options) {
    this.initOptions(options);
    this.initUIBase();
    this.Stateful_init();
    if (this.subMenu && !(this.subMenu instanceof Menu)) {
      if (options.className && options.className.indexOf("aligntd") != -1) {
        var me = this;

        //获取单元格对齐初始状态
        this.subMenu.selected = this.editor.queryCommandValue("cellalignment");

        this.subMenu = new Popup({
          content: new CellAlignPicker(this.subMenu),
          parentMenu: me,
          editor: me.editor,
          destroy: function() {
            if (this.getDom()) {
              domUtils.remove(this.getDom());
            }
          }
        });
        this.subMenu.addListener("postRenderAfter", function() {
          domUtils.on(this.getDom(), "mouseover", function() {
            me.addState("opened");
          });
        });
      } else {
        this.subMenu = new Menu(this.subMenu);
      }
    }
  });
  MenuItem.prototype = {
    label: "",
    subMenu: null,
    ownerMenu: null,
    uiName: "menuitem",
    alwalysHoverable: true,
    getHtmlTpl: function() {
      return (
        '<div id="##" class="%%" stateful onclick="$$._onClick(event, this);">' +
        '<div class="%%-body">' +
        this.renderLabelHtml() +
        "</div>" +
        "</div>"
      );
    },
    postRender: function() {
      var me = this;
      this.addListener("over", function() {
        me.ownerMenu.fireEvent("submenuover", me);
        if (me.subMenu) {
          me.delayShowSubMenu();
        }
      });
      if (this.subMenu) {
        this.getDom().className += " edui-hassubmenu";
        this.subMenu.render();
        this.addListener("out", function() {
          me.delayHideSubMenu();
        });
        this.subMenu.addListener("over", function() {
          clearTimeout(me._closingTimer);
          me._closingTimer = null;
          me.addState("opened");
        });
        this.ownerMenu.addListener("hide", function() {
          me.hideSubMenu();
        });
        this.ownerMenu.addListener("submenuover", function(t, subMenu) {
          if (subMenu !== me) {
            me.delayHideSubMenu();
          }
        });
        this.subMenu._bakQueryAutoHide = this.subMenu.queryAutoHide;
        this.subMenu.queryAutoHide = function(el) {
          if (el && uiUtils.contains(me.getDom(), el)) {
            return false;
          }
          return this._bakQueryAutoHide(el);
        };
      }
      this.getDom().style.tabIndex = "-1";
      uiUtils.makeUnselectable(this.getDom());
      this.Stateful_postRender();
    },
    delayShowSubMenu: function() {
      var me = this;
      if (!me.isDisabled()) {
        me.addState("opened");
        clearTimeout(me._showingTimer);
        clearTimeout(me._closingTimer);
        me._closingTimer = null;
        me._showingTimer = setTimeout(function() {
          me.showSubMenu();
        }, 250);
      }
    },
    delayHideSubMenu: function() {
      var me = this;
      if (!me.isDisabled()) {
        me.removeState("opened");
        clearTimeout(me._showingTimer);
        if (!me._closingTimer) {
          me._closingTimer = setTimeout(function() {
            if (!me.hasState("opened")) {
              me.hideSubMenu();
            }
            me._closingTimer = null;
          }, 400);
        }
      }
    },
    renderLabelHtml: function() {
      return (
        '<div class="edui-arrow"></div>' +
        '<div class="edui-box edui-icon"></div>' +
        '<div class="edui-box edui-label %%-label">' +
        (this.label || "") +
        "</div>"
      );
    },
    getStateDom: function() {
      return this.getDom();
    },
    queryAutoHide: function(el) {
      if (this.subMenu && this.hasState("opened")) {
        return this.subMenu.queryAutoHide(el);
      }
    },
    _onClick: function(event, this_) {
      if (this.hasState("disabled")) return;
      if (this.fireEvent("click", event, this_) !== false) {
        if (this.subMenu) {
          this.showSubMenu();
        } else {
          Popup.postHide(event);
        }
      }
    },
    showSubMenu: function() {
      var rect = uiUtils.getClientRect(this.getDom());
      rect.right -= 5;
      rect.left += 2;
      rect.width -= 7;
      rect.top -= 4;
      rect.bottom += 4;
      rect.height += 8;
      this.subMenu.showAnchorRect(rect, true, true);
    },
    hideSubMenu: function() {
      this.subMenu.hide();
    }
  };
  utils.inherits(MenuItem, UIBase);
  utils.extend(MenuItem.prototype, Stateful, true);
})();


// ui/combox.js
///import core
///import uicore
///import ui/menu.js
///import ui/splitbutton.js
(function() {
  // todo: menu和item提成通用list
  var utils = baidu.editor.utils,
    uiUtils = baidu.editor.ui.uiUtils,
    Menu = baidu.editor.ui.Menu,
    SplitButton = baidu.editor.ui.SplitButton,
    Combox = (baidu.editor.ui.Combox = function(options) {
      this.initOptions(options);
      this.initCombox();
    });
  Combox.prototype = {
    uiName: "combox",
    onbuttonclick: function() {
      this.showPopup();
    },
    initCombox: function() {
      var me = this;
      this.items = this.items || [];
      for (var i = 0; i < this.items.length; i++) {
        var item = this.items[i];
        item.uiName = "listitem";
        item.index = i;
        item.onclick = function() {
          me.selectByIndex(this.index);
        };
      }
      this.popup = new Menu({
        items: this.items,
        uiName: "list",
        editor: this.editor,
        captureWheel: true,
        combox: this
      });

      this.initSplitButton();
    },
    _SplitButton_postRender: SplitButton.prototype.postRender,
    postRender: function() {
      this._SplitButton_postRender();
      this.setLabel(this.label || "");
      this.setValue(this.initValue || "");
    },
    showPopup: function() {
      var rect = uiUtils.getClientRect(this.getDom());
      rect.top += 1;
      rect.bottom -= 1;
      rect.height -= 2;
      this.popup.showAnchorRect(rect);
    },
    getValue: function() {
      return this.value;
    },
    setValue: function(value) {
      var index = this.indexByValue(value);
      if (index != -1) {
        this.selectedIndex = index;
        this.setLabel(this.items[index].label);
        this.value = this.items[index].value;
      } else {
        this.selectedIndex = -1;
        this.setLabel(this.getLabelForUnknowValue(value));
        this.value = value;
      }
    },
    setLabel: function(label) {
      this.getDom("button_body").innerHTML = label;
      this.label = label;
    },
    getLabelForUnknowValue: function(value) {
      return value;
    },
    indexByValue: function(value) {
      for (var i = 0; i < this.items.length; i++) {
        if (value == this.items[i].value) {
          return i;
        }
      }
      return -1;
    },
    getItem: function(index) {
      return this.items[index];
    },
    selectByIndex: function(index) {
      if (
        index < this.items.length &&
        this.fireEvent("select", index) !== false
      ) {
        this.selectedIndex = index;
        this.value = this.items[index].value;
        this.setLabel(this.items[index].label);
      }
    }
  };
  utils.inherits(Combox, SplitButton);
})();


// ui/dialog.js
///import core
///import uicore
///import ui/mask.js
///import ui/button.js
(function() {
  var utils = baidu.editor.utils,
    domUtils = baidu.editor.dom.domUtils,
    uiUtils = baidu.editor.ui.uiUtils,
    Mask = baidu.editor.ui.Mask,
    UIBase = baidu.editor.ui.UIBase,
    Button = baidu.editor.ui.Button,
    Dialog = (baidu.editor.ui.Dialog = function(options) {
      if (options.name) {
        var name = options.name;
        var cssRules = options.cssRules;
        if (!options.className) {
          options.className = "edui-for-" + name;
        }
        if (cssRules) {
          options.cssRules =
            ".edui-for-" + name + " .edui-dialog-content  {" + cssRules + "}";
        }
      }
      this.initOptions(
        utils.extend(
          {
            autoReset: true,
            draggable: true,
            onok: function() {},
            oncancel: function() {},
            onclose: function(t, ok) {
              return ok ? this.onok() : this.oncancel();
            },
            //是否控制dialog中的scroll事件， 默认为不阻止
            holdScroll: false
          },
          options
        )
      );
      this.initDialog();
    });
  var modalMask;
  var dragMask;
  var activeDialog;
  Dialog.prototype = {
    draggable: false,
    uiName: "dialog",
    initDialog: function() {
      var me = this,
        theme = this.editor.options.theme;
      if (this.cssRules) {
        this.cssRules = ".edui-" + theme + " " + this.cssRules;
        utils.cssRule("edui-customize-" + this.name + "-style", this.cssRules);
      }
      this.initUIBase();
      this.modalMask =
        modalMask ||
        (modalMask = new Mask({
          className: "edui-dialog-modalmask",
          theme: theme,
          onclick: function() {
            activeDialog && activeDialog.close(false);
          }
        }));
      this.dragMask =
        dragMask ||
        (dragMask = new Mask({
          className: "edui-dialog-dragmask",
          theme: theme
        }));
      this.closeButton = new Button({
        className: "edui-dialog-closebutton",
        title: me.closeDialog,
        theme: theme,
        onclick: function() {
          me.close(false);
        }
      });

      this.fullscreen && this.initResizeEvent();

      if (this.buttons) {
        for (var i = 0; i < this.buttons.length; i++) {
          if (!(this.buttons[i] instanceof Button)) {
            this.buttons[i] = new Button(
              utils.extend(
                this.buttons[i],
                {
                  editor: this.editor
                },
                true
              )
            );
          }
        }
      }
    },
    initResizeEvent: function() {
      var me = this;



      domUtils.on(window, "resize", function() {

        if (me._hidden || me._hidden === undefined) {
          return;
        }

        if (me.__resizeTimer) {
          window.clearTimeout(me.__resizeTimer);
        }

        me.__resizeTimer = window.setTimeout(function() {
          me.__resizeTimer = null;



          var dialogWrapNode = me.getDom(),
            contentNode = me.getDom("content"),
            wrapRect = UE.ui.uiUtils.getClientRect(dialogWrapNode),
            contentRect = UE.ui.uiUtils.getClientRect(contentNode),
            vpRect = uiUtils.getViewportRect();

          contentNode.style.width =
            vpRect.width - wrapRect.width + contentRect.width + "px";
          contentNode.style.height =
            vpRect.height - wrapRect.height + contentRect.height + "px";

          dialogWrapNode.style.width = vpRect.width + "px";
          dialogWrapNode.style.height = vpRect.height + "px";

          me.fireEvent("resize");
        }, 100);
      });
    },
    fitSize: function() {
      // console.log('fitSize.dialog')
      var popBodyEl = this.getDom("body");
      var $foot = popBodyEl.querySelector('.edui-dialog-foot');
      var heightWithoutBody = 70;
      if(!$foot){
         heightWithoutBody = 30;
      }
      var size = this.mesureSize();
      var winSize = uiUtils.getViewportRect();
      var width = size.width;
      var height = size.height - heightWithoutBody;
      var maxWidth = winSize.width - 2;
      var maxHeight = winSize.height - heightWithoutBody - 2;
      if(width > maxWidth){
        height = height * maxWidth / width;
        width = maxWidth;
      }
      if(height > maxHeight){
        width = width * maxHeight / height;
        height = maxHeight;
      }
      var scale = (width / size.width);
      // console.log('size', {sizeWidth:size.width,sizeHeight:size.height,width,height,scale});
      // console.log('popBodyEl',popBodyEl, popBodyEl.querySelector('.edui-dialog-foot'));
      // window._xxx = popBodyEl;
      var $content = popBodyEl.querySelector('.edui-dialog-content');
      if(!$content.dataset.dialogScaled){
        $content.dataset.dialogScaled = true
        $content.style.width = (width)+'px';
        $content.style.height = (height)+'px';
        var $iframe = popBodyEl.querySelector('.edui-dialog-content iframe');
        $iframe.style.width = (size.width) +'px';
        $iframe.style.height = (size.height) +'px';
        $iframe.style.transformOrigin = '0 0';
        $iframe.style.transform = 'scale('+scale+')';
        size.width = width
        size.height = height + heightWithoutBody
      }
      popBodyEl.style.width = size.width + "px";
      popBodyEl.style.height = size.height + "px";
      return size;
    },
    safeSetOffset: function(offset) {
      var me = this;
      var el = me.getDom();
      var vpRect = uiUtils.getViewportRect();
      var rect = uiUtils.getClientRect(el);
      var left = offset.left;
      if (left + rect.width > vpRect.right) {
        left = vpRect.right - rect.width;
      }
      var top = offset.top;
      if (top + rect.height > vpRect.bottom) {
        top = vpRect.bottom - rect.height;
      }
      el.style.left = Math.max(left, 0) + "px";
      el.style.top = Math.max(top, 0) + "px";
    },
    showAtCenter: function() {
      var vpRect = uiUtils.getViewportRect();

      if (!this.fullscreen) {
        this.getDom().style.display = "";
        var popSize = this.fitSize();
        var titleHeight = this.getDom("titlebar").offsetHeight | 0;
        var left = vpRect.width / 2 - popSize.width / 2;
        var top =
          vpRect.height / 2 - (popSize.height - titleHeight) / 2 - titleHeight;
        var popEl = this.getDom();
        this.safeSetOffset({
          left: Math.max(left | 0, 0),
          top: Math.max(top | 0, 0)
        });
        if (!domUtils.hasClass(popEl, "edui-state-centered")) {
          popEl.className += " edui-state-centered";
        }
      } else {
        var dialogWrapNode = this.getDom(),
          contentNode = this.getDom("content");

        dialogWrapNode.style.display = "block";

        var wrapRect = UE.ui.uiUtils.getClientRect(dialogWrapNode),
          contentRect = UE.ui.uiUtils.getClientRect(contentNode);
        dialogWrapNode.style.left = "-100000px";

        contentNode.style.width =
          vpRect.width - wrapRect.width + contentRect.width + "px";
        contentNode.style.height =
          vpRect.height - wrapRect.height + contentRect.height + "px";

        dialogWrapNode.style.width = vpRect.width + "px";
        dialogWrapNode.style.height = vpRect.height + "px";
        dialogWrapNode.style.left = 0;

        //保存环境的overflow值
        this._originalContext = {
          html: {
            overflowX: document.documentElement.style.overflowX,
            overflowY: document.documentElement.style.overflowY
          },
          body: {
            overflowX: document.body.style.overflowX,
            overflowY: document.body.style.overflowY
          }
        };

        document.documentElement.style.overflowX = "hidden";
        document.documentElement.style.overflowY = "hidden";
        document.body.style.overflowX = "hidden";
        document.body.style.overflowY = "hidden";
      }

      this._show();
    },
    getContentHtml: function() {
      var contentHtml = "";
      if (typeof this.content == "string") {
        contentHtml = this.content;
      } else if (this.iframeUrl) {
        contentHtml =
          '<span id="' +
          this.id +
          '_contmask" class="dialogcontmask"></span><iframe id="' +
          this.id +
          '_iframe" class="%%-iframe" height="100%" width="100%" frameborder="0" src="' +
          this.iframeUrl +
          '"></iframe>';
      }
      return contentHtml;
    },
    getHtmlTpl: function() {
      var footHtml = "";

      if (this.buttons) {
        var buff = [];
        for (var i = 0; i < this.buttons.length; i++) {
          buff[i] = this.buttons[i].renderHtml();
        }
        footHtml =
          '<div class="%%-foot">' +
          '<div id="##_buttons" class="%%-buttons">' +
          buff.join("") +
          "</div>" +
          "</div>";
      }

      return (
        '<div id="##" class="%%"><div ' +
        (!this.fullscreen
          ? 'class="%%"'
          : 'class="%%-wrap edui-dialog-fullscreen-flag"') +
        '><div id="##_body" class="%%-body">' +
        '<div class="%%-shadow"></div>' +
        '<div id="##_titlebar" class="%%-titlebar">' +
        '<div class="%%-draghandle" onmousedown="$$._onTitlebarMouseDown(event, this);">' +
        '<span class="%%-caption">' +
        (this.title || "") +
        "</span>" +
        "</div>" +
        this.closeButton.renderHtml() +
        "</div>" +
        '<div id="##_content" class="%%-content">' +
        (this.autoReset ? "" : this.getContentHtml()) +
        "</div>" +
        footHtml +
        "</div></div></div>"
      );
    },
    postRender: function() {
      // todo: 保持居中/记住上次关闭位置选项
      if (!this.modalMask.getDom()) {
        this.modalMask.render();
        this.modalMask.hide();
      }
      if (!this.dragMask.getDom()) {
        this.dragMask.render();
        this.dragMask.hide();
      }
      var me = this;
      this.addListener("show", function() {
        me.modalMask.show(this.getDom().style.zIndex - 2);
      });
      this.addListener("hide", function() {
        me.modalMask.hide();
      });
      if (this.buttons) {
        for (var i = 0; i < this.buttons.length; i++) {
          this.buttons[i].postRender();
        }
      }
      domUtils.on(window, "resize", function() {
        setTimeout(function() {
          if (!me.isHidden()) {
            me.safeSetOffset(uiUtils.getClientRect(me.getDom()));
          }
        });
      });

      //hold住scroll事件，防止dialog的滚动影响页面
      //            if( this.holdScroll ) {
      //
      //                if( !me.iframeUrl ) {
      //                    domUtils.on( document.getElementById( me.id + "_iframe"), !browser.gecko ? "mousewheel" : "DOMMouseScroll", function(e){
      //                        domUtils.preventDefault(e);
      //                    } );
      //                } else {
      //                    me.addListener('dialogafterreset', function(){
      //                        window.setTimeout(function(){
      //                            var iframeWindow = document.getElementById( me.id + "_iframe").contentWindow;
      //
      //                            if( browser.ie ) {
      //
      //                                var timer = window.setInterval(function(){
      //
      //                                    if( iframeWindow.document && iframeWindow.document.body ) {
      //                                        window.clearInterval( timer );
      //                                        timer = null;
      //                                        domUtils.on( iframeWindow.document.body, !browser.gecko ? "mousewheel" : "DOMMouseScroll", function(e){
      //                                            domUtils.preventDefault(e);
      //                                        } );
      //                                    }
      //
      //                                }, 100);
      //
      //                            } else {
      //                                domUtils.on( iframeWindow, !browser.gecko ? "mousewheel" : "DOMMouseScroll", function(e){
      //                                    domUtils.preventDefault(e);
      //                                } );
      //                            }
      //
      //                        }, 1);
      //                    });
      //                }
      //
      //            }
      this._hide();
    },
    mesureSize: function() {
      var body = this.getDom("body");
      var width = uiUtils.getClientRect(this.getDom("content")).width;
      var dialogBodyStyle = body.style;
      dialogBodyStyle.width = width;
      return uiUtils.getClientRect(body);
    },
    _onTitlebarMouseDown: function(evt, el) {
      if (this.draggable) {
        var rect;
        var vpRect = uiUtils.getViewportRect();
        var me = this;
        uiUtils.startDrag(evt, {
          ondragstart: function() {
            rect = uiUtils.getClientRect(me.getDom());
            me.getDom("contmask").style.visibility = "visible";
            me.dragMask.show(me.getDom().style.zIndex - 1);
          },
          ondragmove: function(x, y) {
            var left = rect.left + x;
            var top = rect.top + y;
            me.safeSetOffset({
              left: left,
              top: top
            });
          },
          ondragstop: function() {
            me.getDom("contmask").style.visibility = "hidden";
            domUtils.removeClasses(me.getDom(), ["edui-state-centered"]);
            me.dragMask.hide();
          }
        });
      }
    },
    reset: function() {
      this.getDom("content").innerHTML = this.getContentHtml();
      this.fireEvent("dialogafterreset");
    },
    _show: function() {
      if (this._hidden) {
        this.getDom().style.display = "";

        //要高过编辑器的zindxe
        this.editor.container.style.zIndex &&
          (this.getDom().style.zIndex =
            this.editor.container.style.zIndex * 1 + 10);
        this._hidden = false;
        this.fireEvent("show");
        baidu.editor.ui.uiUtils.getFixedLayer().style.zIndex =
          this.getDom().style.zIndex - 4;
      }
    },
    isHidden: function() {
      return this._hidden;
    },
    _hide: function() {
      if (!this._hidden) {
        var wrapNode = this.getDom();
        wrapNode.style.display = "none";
        wrapNode.style.zIndex = "";
        wrapNode.style.width = "";
        wrapNode.style.height = "";
        this._hidden = true;
        this.fireEvent("hide");
      }
    },
    open: function() {
      if (this.autoReset) {
        //有可能还没有渲染
        try {
          this.reset();
        } catch (e) {
          this.render();
          this.open();
        }
      }
      this.showAtCenter();
      if (this.iframeUrl) {
        try {
          this.getDom("iframe").focus();
        } catch (ex) {}
      }
      activeDialog = this;
    },
    _onCloseButtonClick: function(evt, el) {
      this.close(false);
    },
    close: function(ok) {
      if (this.fireEvent("close", ok) !== false) {
        //还原环境
        if (this.fullscreen) {
          document.documentElement.style.overflowX = this._originalContext.html.overflowX;
          document.documentElement.style.overflowY = this._originalContext.html.overflowY;
          document.body.style.overflowX = this._originalContext.body.overflowX;
          document.body.style.overflowY = this._originalContext.body.overflowY;
          delete this._originalContext;
        }
        this._hide();

        //销毁content
        var content = this.getDom("content");
        var iframe = this.getDom("iframe");
        if (content && iframe) {
          var doc = iframe.contentDocument || iframe.contentWindow.document;
          doc && (doc.body.innerHTML = "");
          domUtils.remove(content);
        }
      }
    }
  };
  utils.inherits(Dialog, UIBase);
})();


// ui/menubutton.js
///import core
///import uicore
///import ui/menu.js
///import ui/splitbutton.js
(function() {
  var utils = baidu.editor.utils,
    Menu = baidu.editor.ui.Menu,
    SplitButton = baidu.editor.ui.SplitButton,
    MenuButton = (baidu.editor.ui.MenuButton = function(options) {
      this.initOptions(options);
      this.initMenuButton();
    });
  MenuButton.prototype = {
    initMenuButton: function() {
      var me = this;
      this.uiName = "menubutton";
      this.popup = new Menu({
        items: me.items,
        className: me.className,
        editor: me.editor
      });
      this.popup.addListener("show", function() {
        var list = this;
        for (var i = 0; i < list.items.length; i++) {
          list.items[i].removeState("checked");
          if (list.items[i].value == me._value) {
            list.items[i].addState("checked");
            this.value = me._value;
          }
        }
      });
      this.initSplitButton();
    },
    setValue: function(value) {
      this._value = value;
    }
  };
  utils.inherits(MenuButton, SplitButton);
})();


// ui/multiMenu.js
///import core
///import uicore
///commands 表情
(function() {
  var utils = baidu.editor.utils,
    Popup = baidu.editor.ui.Popup,
    SplitButton = baidu.editor.ui.SplitButton,
    MultiMenuPop = (baidu.editor.ui.MultiMenuPop = function(options) {
      this.initOptions(options);
      this.initMultiMenu();
    });

  MultiMenuPop.prototype = {
    initMultiMenu: function() {
      var me = this;
      this.popup = new Popup({
        content: "",
        editor: me.editor,
        iframe_rendered: false,
        onshow: function() {
          if (!this.iframe_rendered) {
            this.iframe_rendered = true;
            this.getDom("content").innerHTML =
              '<iframe id="' +
              me.id +
              '_iframe" src="' +
              me.iframeUrl +
              '" frameborder="0"></iframe>';
            me.editor.container.style.zIndex &&
              (this.getDom().style.zIndex =
                me.editor.container.style.zIndex * 1 + 1);
          }
        }
        // canSideUp:false,
        // canSideLeft:false
      });
      this.onbuttonclick = function() {
        this.showPopup();
      };
      this.initSplitButton();
    }
  };

  utils.inherits(MultiMenuPop, SplitButton);
})();


// ui/shortcutmenu.js
(function() {
  var UI = baidu.editor.ui,
    UIBase = UI.UIBase,
    uiUtils = UI.uiUtils,
    utils = baidu.editor.utils,
    domUtils = baidu.editor.dom.domUtils;

  var allMenus = [], //存储所有快捷菜单
    timeID,
    isSubMenuShow = false; //是否有子pop显示

  var ShortCutMenu = (UI.ShortCutMenu = function(options) {
    this.initOptions(options);
    this.initShortCutMenu();
  });

  ShortCutMenu.postHide = hideAllMenu;

  ShortCutMenu.prototype = {
    isHidden: true,
    SPACE: 5,
    initShortCutMenu: function() {
      this.items = this.items || [];
      this.initUIBase();
      this.initItems();
      this.initEvent();
      allMenus.push(this);
    },
    initEvent: function() {
      var me = this,
        doc = me.editor.document;

      /*
      domUtils.on(doc, "mousemove", function(e) {
        if (me.isHidden === false) {
          //有pop显示就不隐藏快捷菜单
          if (me.getSubMenuMark() || me.eventType == "contextmenu") return;

          var flag = true,
            el = me.getDom(),
            wt = el.offsetWidth,
            ht = el.offsetHeight,
            distanceX = wt / 2 + me.SPACE, //距离中心X标准
            distanceY = ht / 2, //距离中心Y标准
            x = Math.abs(e.screenX - me.left), //离中心距离横坐标
            y = Math.abs(e.screenY - me.top); //离中心距离纵坐标

          clearTimeout(timeID);
          timeID = setTimeout(function() {
            if (y > 0 && y < distanceY) {
              me.setOpacity(el, "1");
            } else if (y > distanceY && y < distanceY + 70) {
              me.setOpacity(el, "0.5");
              flag = false;
            } else if (y > distanceY + 70 && y < distanceY + 140) {
              me.hide();
            }

            if (flag && x > 0 && x < distanceX) {
              me.setOpacity(el, "1");
            } else if (x > distanceX && x < distanceX + 70) {
              me.setOpacity(el, "0.5");
            } else if (x > distanceX + 70 && x < distanceX + 140) {
                console.log('hide')
              me.hide();
            }
          });
        }
      });
      */
      //ie\ff下 mouseout不准
      /*
      if (browser.chrome) {
        domUtils.on(doc, "mouseout", function(e) {
          var relatedTgt = e.relatedTarget || e.toElement;

          if (relatedTgt == null || relatedTgt.tagName == "HTML") {
            me.hide();
          }
        });
      }
       */

      me.editor.addListener("afterhidepop", function() {
        if (!me.isHidden) {
          isSubMenuShow = true;
        }
      });
    },
    initItems: function() {
      if (utils.isArray(this.items)) {
        for (var i = 0, len = this.items.length; i < len; i++) {
          var item = this.items[i].toLowerCase();

          if (UI[item]) {
            this.items[i] = new UI[item](this.editor);
            this.items[i].className += " edui-shortcutsubmenu ";
          }
        }
      }
    },
    setOpacity: function(el, value) {
      if (browser.ie && browser.version < 9) {
        el.style.filter = "alpha(opacity = " + parseFloat(value) * 100 + ");";
      } else {
        el.style.opacity = value;
      }
    },
    getSubMenuMark: function() {
      isSubMenuShow = false;
      var layerEle = uiUtils.getFixedLayer();
      var list = domUtils.getElementsByTagName(layerEle, "div", function(node) {
        return domUtils.hasClass(node, "edui-shortcutsubmenu edui-popup");
      });

      for (var i = 0, node; (node = list[i++]); ) {
        if (node.style.display != "none") {
          isSubMenuShow = true;
        }
      }
      return isSubMenuShow;
    },
    show: function(e, hasContextmenu) {
      var me = this,
        offset = {},
        el = this.getDom(),
        fixedlayer = uiUtils.getFixedLayer();

      function setPos(offset) {
        if (offset.left < 0) {
          offset.left = 0;
        }
        if (offset.top < 0) {
          offset.top = 0;
        }
        el.style.cssText =
          "position:absolute;left:" +
          offset.left +
          "px;top:" +
          offset.top +
          "px;";
      }

      function setPosByCxtMenu(menu) {
        if (!menu.tagName) {
          menu = menu.getDom();
        }
        offset.left = parseInt(menu.style.left);
        offset.top = parseInt(menu.style.top);
        offset.top -= el.offsetHeight + 15;
        setPos(offset);
      }

      me.eventType = e.type;
      el.style.cssText = "display:block;left:-9999px";

      if (e.type == "contextmenu" && hasContextmenu) {
        var menu = domUtils.getElementsByTagName(
          fixedlayer,
          "div",
          "edui-contextmenu"
        )[0];
        if (menu) {
          setPosByCxtMenu(menu);
        } else {
          me.editor.addListener("aftershowcontextmenu", function(type, menu) {
            setPosByCxtMenu(menu);
          });
        }
      } else {
        offset = uiUtils.getViewportOffsetByEvent(e);
        offset.top -= el.offsetHeight + me.SPACE;
        offset.left += me.SPACE + 20;
        setPos(offset);
        me.setOpacity(el, 1);
      }

      me.isHidden = false;
      me.left = e.screenX + el.offsetWidth / 2 - me.SPACE;
      me.top = e.screenY - el.offsetHeight / 2 - me.SPACE;

      if (me.editor) {
        el.style.zIndex = me.editor.container.style.zIndex * 1 + 10;
        fixedlayer.style.zIndex = el.style.zIndex - 1;
      }
    },
    hide: function() {
      if (this.getDom()) {
        this.getDom().style.display = "none";
      }
      this.isHidden = true;
    },
    postRender: function() {
      if (utils.isArray(this.items)) {
        for (var i = 0, item; (item = this.items[i++]); ) {
          item.postRender();
        }
      }
    },
    getHtmlTpl: function() {
      var buff;
      if (utils.isArray(this.items)) {
        buff = [];
        for (var i = 0; i < this.items.length; i++) {
          buff[i] = this.items[i].renderHtml();
        }
        buff = buff.join("");
      } else {
        buff = this.items;
      }

      return (
        '<div id="##" class="%% edui-toolbar" data-src="shortcutmenu" onmousedown="return false;" onselectstart="return false;" >' +
        buff +
        "</div>"
      );
    }
  };

  utils.inherits(ShortCutMenu, UIBase);

  function hideAllMenu(e) {
    var tgt = e.target || e.srcElement,
      cur = domUtils.findParent(
        tgt,
        function(node) {
          return (
            domUtils.hasClass(node, "edui-shortcutmenu") ||
            domUtils.hasClass(node, "edui-popup")
          );
        },
        true
      );

    if (!cur) {
      for (var i = 0, menu; (menu = allMenus[i++]); ) {
        menu.hide();
      }
    }
  }

  domUtils.on(document, "mousedown", function(e) {
    hideAllMenu(e);
  });

  domUtils.on(window, "scroll", function(e) {
    hideAllMenu(e);
  });
})();


// ui/breakline.js
(function() {
  var utils = baidu.editor.utils,
    UIBase = baidu.editor.ui.UIBase,
    Breakline = (baidu.editor.ui.Breakline = function(options) {
      this.initOptions(options);
      this.initSeparator();
    });
  Breakline.prototype = {
    uiName: "Breakline",
    initSeparator: function() {
      this.initUIBase();
    },
    getHtmlTpl: function() {
      return "<br/>";
    }
  };
  utils.inherits(Breakline, UIBase);
})();


// ui/message.js
///import core
///import uicore
(function() {
  var utils = baidu.editor.utils,
    domUtils = baidu.editor.dom.domUtils,
    UIBase = baidu.editor.ui.UIBase,
    Message = (baidu.editor.ui.Message = function(options) {
      this.initOptions(options);
      this.initMessage();
    });

  Message.prototype = {
    initMessage: function() {
      this.initUIBase();
    },
    getHtmlTpl: function() {
      return (
        '<div id="##" class="edui-message %%">' +
        ' <div id="##_closer" class="edui-message-closer">×</div>' +
        ' <div id="##_body" class="edui-message-body edui-message-type-info">' +
        ' <iframe style="position:absolute;z-index:-1;left:0;top:0;background-color: transparent;" frameborder="0" width="100%" height="100%" src="about:blank"></iframe>' +
        ' <div class="edui-shadow"></div>' +
        ' <div id="##_content" class="edui-message-content">' +
        "  </div>" +
        " </div>" +
        "</div>"
      );
    },
    reset: function(opt) {
      var me = this;
      if (!opt.keepshow) {
        clearTimeout(this.timer);
        me.timer = setTimeout(function() {
          me.hide();
        }, opt.timeout || 4000);
      }

      opt.content !== undefined && me.setContent(opt.content);
      opt.type !== undefined && me.setType(opt.type);

      me.show();
    },
    postRender: function() {
      var me = this,
        closer = this.getDom("closer");
      closer &&
        domUtils.on(closer, "click", function() {
          me.hide();
        });
    },
    setContent: function(content) {
      this.getDom("content").innerHTML = content;
    },
    setType: function(type) {
      type = type || "info";
      var body = this.getDom("body");
      body.className = body.className.replace(
        /edui-message-type-[\w-]+/,
        "edui-message-type-" + type
      );
    },
    getContent: function() {
      return this.getDom("content").innerHTML;
    },
    getType: function() {
      var arr = this.getDom("body").match(/edui-message-type-([\w-]+)/);
      return arr ? arr[1] : "";
    },
    show: function() {
      this.getDom().style.display = "block";
    },
    hide: function() {
      var dom = this.getDom();
      if (dom) {
        dom.style.display = "none";
        dom.parentNode && dom.parentNode.removeChild(dom);
      }
    }
  };

  utils.inherits(Message, UIBase);
})();


// adapter/editorui.js
//ui跟编辑器的适配層
//那个按钮弹出是dialog，是下拉筐等都是在这个js中配置
//自己写的ui也要在这里配置，放到baidu.editor.ui下边，当编辑器实例化的时候会根据ueditor.config中的toolbars找到相应的进行实例化
(function() {
  var utils = baidu.editor.utils;
  var editorui = baidu.editor.ui;
  var _Dialog = editorui.Dialog;
  editorui.buttons = {};

  editorui.Dialog = function(options) {
    var dialog = new _Dialog(options);
    dialog.addListener("hide", function() {
      if (dialog.editor) {
        var editor = dialog.editor;
        try {
          if (browser.gecko) {
            var y = editor.window.scrollY,
              x = editor.window.scrollX;
            editor.body.focus();
            editor.window.scrollTo(x, y);
          } else {
            editor.focus();
          }
        } catch (ex) {}
      }
    });
    return dialog;
  };

  var iframeUrlMap = {
    anchor: "~/dialogs/anchor/anchor.html?20220503",
    insertimage: "~/dialogs/image/image.html?20220503",
    link: "~/dialogs/link/link.html?20220503",
    spechars: "~/dialogs/spechars/spechars.html?20220503",
    searchreplace: "~/dialogs/searchreplace/searchreplace.html?20220503",
    insertvideo: "~/dialogs/video/video.html?20220503",
    help: "~/dialogs/help/help.html?20220503",
    preview: "~/dialogs/preview/preview.html?20220503",
    emotion: "~/dialogs/emotion/emotion.html?20220503",
    wordimage: "~/dialogs/wordimage/wordimage.html?20220902",
    formula: "~/dialogs/formula/formula.html?20220902",
    attachment: "~/dialogs/attachment/attachment.html?20220503",
    insertframe: "~/dialogs/insertframe/insertframe.html?20220503",
    edittip: "~/dialogs/table/edittip.html?20220503",
    edittable: "~/dialogs/table/edittable.html?20220503",
    edittd: "~/dialogs/table/edittd.html?20220503",
    scrawl: "~/dialogs/scrawl/scrawl.html?20220503",
    template: "~/dialogs/template/template.html?20220503",
    background: "~/dialogs/background/background.html?20220503",
  };
  //为工具栏添加按钮，以下都是统一的按钮触发命令，所以写在一起
  var btnCmds = [
    "undo",
    "redo",
    "formatmatch",
    "bold",
    "italic",
    "underline",
    "fontborder",
    "touppercase",
    "tolowercase",
    "strikethrough",
    "subscript",
    "superscript",
    "source",
    "indent",
    "outdent",
    "blockquote",
    "pasteplain",
    "pagebreak",
    "selectall",
    "print",
    "horizontal",
    "removeformat",
    "time",
    "date",
    "unlink",
    "insertparagraphbeforetable",
    "insertrow",
    "insertcol",
    "mergeright",
    "mergedown",
    "deleterow",
    "deletecol",
    "splittorows",
    "splittocols",
    "splittocells",
    "mergecells",
    "deletetable",
  ];

  for (var i = 0, ci; (ci = btnCmds[i++]); ) {
    ci = ci.toLowerCase();
    editorui[ci] = (function(cmd) {
      return function(editor) {
        var ui = new editorui.Button({
          className: "edui-for-" + cmd,
          title:
            editor.options.labelMap[cmd] ||
              editor.getLang("labelMap." + cmd) ||
              "",
          onclick: function() {
            editor.execCommand(cmd);
          },
          theme: editor.options.theme,
          showText: false
        });
        editorui.buttons[cmd] = ui;
        editor.addListener("selectionchange", function(
          type,
          causeByUi,
          uiReady
        ) {
          var state = editor.queryCommandState(cmd);
          // console.log('selectionchange',cmd,uiReady,state);
          if (state == -1) {
            ui.setDisabled(true);
            ui.setChecked(false);
          } else {
            if (!uiReady) {
              ui.setDisabled(false);
              ui.setChecked(state);
            }
          }
        });
        return ui;
      };
    })(ci);
  }

  //清除文档
  editorui.cleardoc = function(editor) {
    var ui = new editorui.Button({
      className: "edui-for-cleardoc",
      title:
        editor.options.labelMap.cleardoc ||
          editor.getLang("labelMap.cleardoc") ||
          "",
      theme: editor.options.theme,
      onclick: function() {
        if (confirm(editor.getLang("confirmClear"))) {
          editor.execCommand("cleardoc");
        }
      }
    });
    editorui.buttons["cleardoc"] = ui;
    editor.addListener("selectionchange", function() {
      ui.setDisabled(editor.queryCommandState("cleardoc") == -1);
    });
    return ui;
  };

  //排版，图片排版，文字方向
  var typeset = {
    justify: ["left", "right", "center", "justify"],
    imagefloat: ["none", "left", "center", "right"],
    directionality: ["ltr", "rtl"]
  };

  for (var p in typeset) {
    (function(cmd, val) {
      for (var i = 0, ci; (ci = val[i++]); ) {
        (function(cmd2) {
          editorui[cmd.replace("float", "") + cmd2] = function(editor) {
            var ui = new editorui.Button({
              className: "edui-for-" + cmd.replace("float", "") + cmd2,
              title:
                editor.options.labelMap[cmd.replace("float", "") + cmd2] ||
                  editor.getLang(
                    "labelMap." + cmd.replace("float", "") + cmd2
                  ) ||
                  "",
              theme: editor.options.theme,
              onclick: function() {
                editor.execCommand(cmd, cmd2);
              }
            });
            editorui.buttons[cmd] = ui;
            editor.addListener("selectionchange", function(
              type,
              causeByUi,
              uiReady
            ) {
              ui.setDisabled(editor.queryCommandState(cmd) == -1);
              ui.setChecked(editor.queryCommandValue(cmd) == cmd2 && !uiReady);
            });
            return ui;
          };
        })(ci);
      }
    })(p, typeset[p]);
  }

  //字体颜色和背景颜色
  for (var i = 0, ci; (ci = ["backcolor", "forecolor"][i++]); ) {
    editorui[ci] = (function(cmd) {
      return function(editor) {
        var ui = new editorui.ColorButton({
          className: "edui-for-" + cmd,
          color: "default",
          title:
            editor.options.labelMap[cmd] ||
              editor.getLang("labelMap." + cmd) ||
              "",
          editor: editor,
          onpickcolor: function(t, color) {
            editor.execCommand(cmd, color);
          },
          onpicknocolor: function() {
            editor.execCommand(cmd, "default");
            this.setColor("transparent");
            this.color = "default";
          },
          onbuttonclick: function() {
            editor.execCommand(cmd, this.color);
          }
        });
        editorui.buttons[cmd] = ui;
        editor.addListener("selectionchange", function() {
          ui.setDisabled(editor.queryCommandState(cmd) == -1);
        });
        return ui;
      };
    })(ci);
  }

  var dialogBtns = {
    noOk: ["searchreplace", "help", "spechars", "preview"],
    ok: [
      "attachment",
      "anchor",
      "link",
      "insertimage",
      "insertframe",
      "wordimage",
      "insertvideo",
      "insertframe",
      "edittip",
      "edittable",
      "edittd",
      "scrawl",
      "template",
      "formula",
      "background",
    ]
  };

  for (var p in dialogBtns) {
    (function(type, vals) {
      for (var i = 0, ci; (ci = vals[i++]); ) {
        //todo opera下存在问题
        if (browser.opera && ci === "searchreplace") {
          continue;
        }
        (function(cmd) {
          editorui[cmd] = function(editor, iframeUrl, title) {
            iframeUrl =
              iframeUrl ||
              (editor.options.iframeUrlMap || {})[cmd] ||
              iframeUrlMap[cmd];
            title =
              editor.options.labelMap[cmd] ||
              editor.getLang("labelMap." + cmd) ||
              "";

            var dialog;
            //没有iframeUrl不创建dialog
            if (iframeUrl) {
              dialog = new editorui.Dialog(
                utils.extend(
                  {
                    iframeUrl: editor.ui.mapUrl(iframeUrl),
                    editor: editor,
                    className: "edui-for-" + cmd,
                    title: title,
                    holdScroll: cmd === "insertimage",
                    fullscreen: /preview/.test(cmd),
                    closeDialog: editor.getLang("closeDialog")
                  },
                  type == "ok"
                    ? {
                        buttons: [
                          {
                            className: "edui-okbutton",
                            label: editor.getLang("ok"),
                            editor: editor,
                            onclick: function() {
                              dialog.close(true);
                            }
                          },
                          {
                            className: "edui-cancelbutton",
                            label: editor.getLang("cancel"),
                            editor: editor,
                            onclick: function() {
                              dialog.close(false);
                            }
                          }
                        ]
                      }
                    : {}
                )
              );

              editor.ui._dialogs[cmd + "Dialog"] = dialog;
            }

            var ui = new editorui.Button({
              className: "edui-for-" + cmd,
              title: title,
              onclick: function() {
                if(editor.options.toolbarCallback){
                    if(true === editor.options.toolbarCallback(cmd,editor)){
                       return;
                    }
                }
                if (dialog) {
                  switch (cmd) {
                    case "wordimage":
                      var images = editor.execCommand("wordimage");
                      if (images && images.length) {
                        dialog.render();
                        dialog.open();
                      }
                      break;
                    case "scrawl":
                      if (editor.queryCommandState("scrawl") != -1) {
                        dialog.render();
                        dialog.open();
                      }

                      break;
                    default:
                      dialog.render();
                      dialog.open();
                  }
                }
              },
              theme: editor.options.theme,
              disabled:
                (cmd == "scrawl" && editor.queryCommandState("scrawl") == -1)
            });
            editorui.buttons[cmd] = ui;
            editor.addListener("selectionchange", function() {
              //只存在于右键菜单而无工具栏按钮的ui不需要检测状态
              var unNeedCheckState = { edittable: 1 };
              if (cmd in unNeedCheckState) return;

              var state = editor.queryCommandState(cmd);
              if (ui.getDom()) {
                ui.setDisabled(state == -1);
                ui.setChecked(state);
              }
            });

            return ui;
          };
        })(ci.toLowerCase());
      }
    })(p, dialogBtns[p]);
  }

  editorui.insertcode = function(editor, list, title) {
    list = editor.options["insertcode"] || [];
    title =
      editor.options.labelMap["insertcode"] ||
      editor.getLang("labelMap.insertcode") ||
      "";
    // if (!list.length) return;
    var items = [];
    utils.each(list, function(key, val) {
      items.push({
        label: key,
        value: val,
        theme: editor.options.theme,
        renderLabelHtml: function() {
          return (
            '<div class="edui-label %%-label" >' + (this.label || "") + "</div>"
          );
        }
      });
    });

    var ui = new editorui.Combox({
      editor: editor,
      items: items,
      onselect: function(t, index) {
        editor.execCommand("insertcode", this.items[index].value);
      },
      onbuttonclick: function() {
        this.showPopup();
      },
      title: title,
      initValue: title,
      className: "edui-for-insertcode",
      indexByValue: function(value) {
        if (value) {
          for (var i = 0, ci; (ci = this.items[i]); i++) {
            if (ci.value.indexOf(value) != -1) return i;
          }
        }

        return -1;
      }
    });
    editorui.buttons["insertcode"] = ui;
    editor.addListener("selectionchange", function(type, causeByUi, uiReady) {
      if (!uiReady) {
        var state = editor.queryCommandState("insertcode");
        if (state == -1) {
          ui.setDisabled(true);
        } else {
          ui.setDisabled(false);
          var value = editor.queryCommandValue("insertcode");
          if (!value) {
            ui.setValue(title);
            return;
          }
          //trace:1871 ie下从源码模式切换回来时，字体会带单引号，而且会有逗号
          value && (value = value.replace(/['"]/g, "").split(",")[0]);
          ui.setValue(value);
        }
      }
    });
    return ui;
  };
  editorui.fontfamily = function(editor, list, title) {
    list = editor.options["fontfamily"] || [];
    title =
      editor.options.labelMap["fontfamily"] ||
      editor.getLang("labelMap.fontfamily") ||
      "";
    if (!list.length) return;
    for (var i = 0, ci, items = []; (ci = list[i]); i++) {
      var langLabel = editor.getLang("fontfamily")[ci.name] || "";
      (function(key, val) {
        items.push({
          label: key,
          value: val,
          theme: editor.options.theme,
          renderLabelHtml: function() {
            return (
              '<div class="edui-label %%-label" style="font-family:' +
              utils.unhtml(this.value) +
              '">' +
              (this.label || "") +
              "</div>"
            );
          }
        });
      })(ci.label || langLabel, ci.val);
    }
    var ui = new editorui.Combox({
      editor: editor,
      items: items,
      onselect: function(t, index) {
        editor.execCommand("FontFamily", this.items[index].value);
      },
      onbuttonclick: function() {
        this.showPopup();
      },
      title: title,
      initValue: title,
      className: "edui-for-fontfamily",
      indexByValue: function(value) {
        if (value) {
          for (var i = 0, ci; (ci = this.items[i]); i++) {
            if (ci.value.indexOf(value) != -1) return i;
          }
        }
        return -1;
      }
    });
    editorui.buttons["fontfamily"] = ui;
    editor.addListener("selectionchange", function(type, causeByUi, uiReady) {
      if (!uiReady) {
        var state = editor.queryCommandState("FontFamily");
        if (state == -1) {
          ui.setDisabled(true);
        } else {
          ui.setDisabled(false);
          var value = editor.queryCommandValue("FontFamily");
          //trace:1871 ie下从源码模式切换回来时，字体会带单引号，而且会有逗号
          value && (value = value.replace(/['"]/g, "").split(",")[0]);
          ui.setValue(value);
        }
      }
    });
    return ui;
  };

  editorui.fontsize = function(editor, list, title) {
    title =
      editor.options.labelMap["fontsize"] ||
      editor.getLang("labelMap.fontsize") ||
      "";
    list = list || editor.options["fontsize"] || [];
    if (!list.length) return;
    var items = [];
    for (var i = 0; i < list.length; i++) {
      var size = list[i] + "px";
      items.push({
        label: size,
        value: size,
        theme: editor.options.theme,
        renderLabelHtml: function() {
          return (
            '<div class="edui-label %%-label" style="line-height:1;font-size:' +
            this.value +
            '">' +
            (this.label || "") +
            "</div>"
          );
        }
      });
    }
    var ui = new editorui.Combox({
      editor: editor,
      items: items,
      title: title,
      initValue: title,
      onselect: function(t, index) {
        editor.execCommand("FontSize", this.items[index].value);
      },
      onbuttonclick: function() {
        this.showPopup();
      },
      className: "edui-for-fontsize"
    });
    editorui.buttons["fontsize"] = ui;
    editor.addListener("selectionchange", function(type, causeByUi, uiReady) {
      if (!uiReady) {
        var state = editor.queryCommandState("FontSize");
        if (state == -1) {
          ui.setDisabled(true);
        } else {
          ui.setDisabled(false);
          ui.setValue(editor.queryCommandValue("FontSize"));
        }
      }
    });
    return ui;
  };

  editorui.paragraph = function(editor, list, title) {
    title =
      editor.options.labelMap["paragraph"] ||
      editor.getLang("labelMap.paragraph") ||
      "";
    list = editor.options["paragraph"] || [];
    if (utils.isEmptyObject(list)) return;
    var items = [];
    for (var i in list) {
      items.push({
        value: i,
        label: list[i] || editor.getLang("paragraph")[i],
        theme: editor.options.theme,
        renderLabelHtml: function() {
          return (
            '<div class="edui-label %%-label"><span class="edui-for-' +
            this.value +
            '">' +
            (this.label || "") +
            "</span></div>"
          );
        }
      });
    }
    var ui = new editorui.Combox({
      editor: editor,
      items: items,
      title: title,
      initValue: title,
      className: "edui-for-paragraph",
      onselect: function(t, index) {
        editor.execCommand("Paragraph", this.items[index].value);
      },
      onbuttonclick: function() {
        this.showPopup();
      }
    });
    editorui.buttons["paragraph"] = ui;
    editor.addListener("selectionchange", function(type, causeByUi, uiReady) {
      if (!uiReady) {
        var state = editor.queryCommandState("Paragraph");
        if (state == -1) {
          ui.setDisabled(true);
        } else {
          ui.setDisabled(false);
          var value = editor.queryCommandValue("Paragraph");
          var index = ui.indexByValue(value);
          if (index != -1) {
            ui.setValue(value);
          } else {
            ui.setValue(ui.initValue);
          }
        }
      }
    });
    return ui;
  };

  //自定义标题
  editorui.customstyle = function(editor) {
    var list = editor.options["customstyle"] || [],
      title =
        editor.options.labelMap["customstyle"] ||
        editor.getLang("labelMap.customstyle") ||
        "";
    if (!list.length) return;
    var langCs = editor.getLang("customstyle");
    for (var i = 0, items = [], t; (t = list[i++]); ) {
      (function(t) {
        var ck = {};
        ck.label = t.label ? t.label : langCs[t.name];
        ck.style = t.style;
        ck.className = t.className;
        ck.tag = t.tag;
        items.push({
          label: ck.label,
          value: ck,
          theme: editor.options.theme,
          renderLabelHtml: function() {
            return (
              '<div class="edui-label %%-label">' +
              "<" +
              ck.tag +
              " " +
              (ck.className ? ' class="' + ck.className + '"' : "") +
              (ck.style ? ' style="' + ck.style + '"' : "") +
              ">" +
              ck.label +
              "</" +
              ck.tag +
              ">" +
              "</div>"
            );
          }
        });
      })(t);
    }

    var ui = new editorui.Combox({
      editor: editor,
      items: items,
      title: title,
      initValue: title,
      className: "edui-for-customstyle",
      onselect: function(t, index) {
        editor.execCommand("customstyle", this.items[index].value);
      },
      onbuttonclick: function() {
        this.showPopup();
      },
      indexByValue: function(value) {
        for (var i = 0, ti; (ti = this.items[i++]); ) {
          if (ti.label == value) {
            return i - 1;
          }
        }
        return -1;
      }
    });
    editorui.buttons["customstyle"] = ui;
    editor.addListener("selectionchange", function(type, causeByUi, uiReady) {
      if (!uiReady) {
        var state = editor.queryCommandState("customstyle");
        if (state == -1) {
          ui.setDisabled(true);
        } else {
          ui.setDisabled(false);
          var value = editor.queryCommandValue("customstyle");
          var index = ui.indexByValue(value);
          if (index != -1) {
            ui.setValue(value);
          } else {
            ui.setValue(ui.initValue);
          }
        }
      }
    });
    return ui;
  };
  editorui.inserttable = function(editor, iframeUrl, title) {
    title =
      editor.options.labelMap["inserttable"] ||
      editor.getLang("labelMap.inserttable") ||
      "";
    var ui = new editorui.TableButton({
      editor: editor,
      title: title,
      className: "edui-for-inserttable",
      onpicktable: function(t, numCols, numRows) {
        editor.execCommand("InsertTable", {
          numRows: numRows,
          numCols: numCols,
          border: 1
        });
      },
      onbuttonclick: function() {
        this.showPopup();
      }
    });
    editorui.buttons["inserttable"] = ui;
    editor.addListener("selectionchange", function() {
      ui.setDisabled(editor.queryCommandState("inserttable") == -1);
    });
    return ui;
  };

  editorui.lineheight = function(editor) {
    var val = editor.options.lineheight || [];
    if (!val.length) return;
    for (var i = 0, ci, items = []; (ci = val[i++]); ) {
      items.push({
        //todo:写死了
        label: ci,
        value: ci,
        theme: editor.options.theme,
        onclick: function() {
          editor.execCommand("lineheight", this.value);
        }
      });
    }
    var ui = new editorui.MenuButton({
      editor: editor,
      className: "edui-for-lineheight",
      title:
        editor.options.labelMap["lineheight"] ||
          editor.getLang("labelMap.lineheight") ||
          "",
      items: items,
      onbuttonclick: function() {
        var value = editor.queryCommandValue("LineHeight") || this.value;
        editor.execCommand("LineHeight", value);
      }
    });
    editorui.buttons["lineheight"] = ui;
    editor.addListener("selectionchange", function() {
      var state = editor.queryCommandState("LineHeight");
      if (state == -1) {
        ui.setDisabled(true);
      } else {
        ui.setDisabled(false);
        var value = editor.queryCommandValue("LineHeight");
        value && ui.setValue((value + "").replace(/cm/, ""));
        ui.setChecked(state);
      }
    });
    return ui;
  };

  var rowspacings = ["top", "bottom"];
  for (var r = 0, ri; (ri = rowspacings[r++]); ) {
    (function(cmd) {
      editorui["rowspacing" + cmd] = function(editor) {
        var val = editor.options["rowspacing" + cmd] || [];
        if (!val.length) return null;
        for (var i = 0, ci, items = []; (ci = val[i++]); ) {
          items.push({
            label: ci,
            value: ci,
            theme: editor.options.theme,
            onclick: function() {
              editor.execCommand("rowspacing", this.value, cmd);
            }
          });
        }
        var ui = new editorui.MenuButton({
          editor: editor,
          className: "edui-for-rowspacing" + cmd,
          title:
            editor.options.labelMap["rowspacing" + cmd] ||
              editor.getLang("labelMap.rowspacing" + cmd) ||
              "",
          items: items,
          onbuttonclick: function() {
            var value =
              editor.queryCommandValue("rowspacing", cmd) || this.value;
            editor.execCommand("rowspacing", value, cmd);
          }
        });
        editorui.buttons[cmd] = ui;
        editor.addListener("selectionchange", function() {
          var state = editor.queryCommandState("rowspacing", cmd);
          if (state == -1) {
            ui.setDisabled(true);
          } else {
            ui.setDisabled(false);
            var value = editor.queryCommandValue("rowspacing", cmd);
            value && ui.setValue((value + "").replace(/%/, ""));
            ui.setChecked(state);
          }
        });
        return ui;
      };
    })(ri);
  }
  //有序，无序列表
  var lists = ["insertorderedlist", "insertunorderedlist"];
  for (var l = 0, cl; (cl = lists[l++]); ) {
    (function(cmd) {
      editorui[cmd] = function(editor) {
        var vals = editor.options[cmd],
          _onMenuClick = function() {
            editor.execCommand(cmd, this.value);
          },
          items = [];
        for (var i in vals) {
          items.push({
            label: vals[i] || editor.getLang()[cmd][i] || "",
            value: i,
            theme: editor.options.theme,
            onclick: _onMenuClick
          });
        }
        var ui = new editorui.MenuButton({
          editor: editor,
          className: "edui-for-" + cmd,
          title: editor.getLang("labelMap." + cmd) || "",
          items: items,
          onbuttonclick: function() {
            var value = editor.queryCommandValue(cmd) || this.value;
            editor.execCommand(cmd, value);
          }
        });
        editorui.buttons[cmd] = ui;
        editor.addListener("selectionchange", function() {
          var state = editor.queryCommandState(cmd);
          if (state == -1) {
            ui.setDisabled(true);
          } else {
            ui.setDisabled(false);
            var value = editor.queryCommandValue(cmd);
            ui.setValue(value);
            ui.setChecked(state);
          }
        });
        return ui;
      };
    })(cl);
  }

  editorui.fullscreen = function(editor, title) {
    title =
      editor.options.labelMap["fullscreen"] ||
      editor.getLang("labelMap.fullscreen") ||
      "";
    var ui = new editorui.Button({
      className: "edui-for-fullscreen",
      title: title,
      theme: editor.options.theme,
      onclick: function() {
        if (editor.ui) {
          editor.ui.setFullScreen(!editor.ui.isFullScreen());
        }
        this.setChecked(editor.ui.isFullScreen());
      }
    });
    editorui.buttons["fullscreen"] = ui;
    editor.addListener("selectionchange", function() {
      var state = editor.queryCommandState("fullscreen");
      ui.setDisabled(state == -1);
      ui.setChecked(editor.ui.isFullScreen());
    });
    return ui;
  };

  // 表情
  editorui["emotion"] = function(editor, iframeUrl) {
    var cmd = "emotion";
    var ui = new editorui.MultiMenuPop({
      title:
        editor.options.labelMap[cmd] ||
          editor.getLang("labelMap." + cmd + "") ||
          "",
      editor: editor,
      className: "edui-for-" + cmd,
      iframeUrl: editor.ui.mapUrl(
        iframeUrl ||
          (editor.options.iframeUrlMap || {})[cmd] ||
          iframeUrlMap[cmd]
      )
    });
    editorui.buttons[cmd] = ui;

    editor.addListener("selectionchange", function() {
      ui.setDisabled(editor.queryCommandState(cmd) == -1);
    });
    return ui;
  };

  editorui.autotypeset = function(editor) {
    var ui = new editorui.AutoTypeSetButton({
      editor: editor,
      title:
        editor.options.labelMap["autotypeset"] ||
          editor.getLang("labelMap.autotypeset") ||
          "",
      className: "edui-for-autotypeset",
      onbuttonclick: function() {
        editor.execCommand("autotypeset");
      }
    });
    editorui.buttons["autotypeset"] = ui;
    editor.addListener("selectionchange", function() {
      ui.setDisabled(editor.queryCommandState("autotypeset") == -1);
    });
    return ui;
  };

  /* 简单上传插件 */
  editorui["simpleupload"] = function(editor) {
    var name = "simpleupload",
      ui = new editorui.Button({
        className: "edui-for-" + name,
        title:
          editor.options.labelMap[name] ||
            editor.getLang("labelMap." + name) ||
            "",
        onclick: function() {},
        theme: editor.options.theme,
        showText: false
      });
    editorui.buttons[name] = ui;
    editor.addListener("ready", function() {
      var b = ui.getDom("body"),
        iconSpan = b.children[0];
      editor.fireEvent("simpleuploadbtnready", iconSpan);
    });
    editor.addListener("selectionchange", function(type, causeByUi, uiReady) {
      var state = editor.queryCommandState(name);
      if (state == -1) {
        ui.setDisabled(true);
        ui.setChecked(false);
      } else {
        if (!uiReady) {
          ui.setDisabled(false);
          ui.setChecked(state);
        }
      }
    });
    return ui;
  };
})();


// adapter/editor.js
///import core
///commands 全屏
///commandsName FullScreen
///commandsTitle  全屏
(function () {
  var utils = baidu.editor.utils,
    uiUtils = baidu.editor.ui.uiUtils,
    UIBase = baidu.editor.ui.UIBase,
    domUtils = baidu.editor.dom.domUtils;
  var nodeStack = [];

  function EditorUI(options) {
    this.initOptions(options);
    this.initEditorUI();
  }

  EditorUI.prototype = {
    uiName: "editor",
    initEditorUI: function () {
      this.editor.ui = this;
      this._dialogs = {};
      this.initUIBase();
      this._initToolbars();
      var editor = this.editor,
        me = this;

      editor.addListener("ready", function () {
        //提供getDialog方法
        editor.getDialog = function (name) {
          return editor.ui._dialogs[name + "Dialog"];
        };
        domUtils.on(editor.window, "scroll", function (evt) {
          baidu.editor.ui.Popup.postHide(evt);
        });
        //提供编辑器实时宽高(全屏时宽高不变化)
        editor.ui._actualFrameWidth = editor.options.initialFrameWidth;

        UE.browser.ie &&
        UE.browser.version === 6 &&
        editor.container.ownerDocument.execCommand(
          "BackgroundImageCache",
          false,
          true
        );

        //display bottom-bar label based on config
        if (editor.options.elementPathEnabled) {
          editor.ui.getDom("elementpath").innerHTML =
            '<div class="edui-editor-breadcrumb">' +
            editor.getLang("elementPathTip") +
            ":</div>";
        }
        if (editor.options.wordCount) {
          function countFn() {
            setCount(editor, me);
            domUtils.un(editor.document, "click", arguments.callee);
          }

          domUtils.on(editor.document, "click", countFn);
          editor.ui.getDom("wordcount").innerHTML = editor.getLang(
            "wordCountTip"
          );
        }
        editor.ui._scale();
        if (editor.options.scaleEnabled) {
          if (editor.autoHeightEnabled) {
            editor.disableAutoHeight();
          }
          me.enableScale();
        } else {
          me.disableScale();
        }
        if (
          !editor.options.elementPathEnabled &&
          !editor.options.wordCount &&
          !editor.options.scaleEnabled
        ) {
          editor.ui.getDom("elementpath").style.display = "none";
          editor.ui.getDom("wordcount").style.display = "none";
          editor.ui.getDom("scale").style.display = "none";
        }

        if (!editor.selection.isFocus()) return;
        editor.fireEvent("selectionchange", false, true);
      });

      editor.addListener("mousedown", function (t, evt) {
        var el = evt.target || evt.srcElement;
        baidu.editor.ui.Popup.postHide(evt, el);
        baidu.editor.ui.ShortCutMenu.postHide(evt);
      });
      editor.addListener("delcells", function () {
        if (UE.ui["edittip"]) {
          new UE.ui["edittip"](editor);
        }
        editor.getDialog("edittip").open();
      });

      var pastePop,
        isPaste = false,
        timer;
      editor.addListener("afterpaste", function () {
        if (editor.queryCommandState("pasteplain")) return;
        if (baidu.editor.ui.PastePicker) {
          pastePop = new baidu.editor.ui.Popup({
            content: new baidu.editor.ui.PastePicker({editor: editor}),
            editor: editor,
            className: "edui-wordpastepop"
          });
          pastePop.render();
        }
        isPaste = true;
      });

      editor.addListener("afterinserthtml", function () {
        clearTimeout(timer);
        timer = setTimeout(function () {
          if (pastePop && (isPaste || editor.ui._isTransfer)) {
            if (pastePop.isHidden()) {
              var span = domUtils.createElement(editor.document, "span", {
                  style: "line-height:0px;",
                  innerHTML: "\ufeff"
                }),
                range = editor.selection.getRange();
              range.insertNode(span);
              var tmp = getDomNode(span, "firstChild", "previousSibling");
              tmp &&
              pastePop.showAnchor(tmp.nodeType == 3 ? tmp.parentNode : tmp);
              domUtils.remove(span);
            } else {
              pastePop.show();
            }
            delete editor.ui._isTransfer;
            isPaste = false;
          }
        }, 200);
      });
      editor.addListener("contextmenu", function (t, evt) {
        baidu.editor.ui.Popup.postHide(evt);
      });
      editor.addListener("keydown", function (t, evt) {
        if (pastePop) pastePop.dispose(evt);
        var keyCode = evt.keyCode || evt.which;
        if (evt.altKey && keyCode == 90) {
          UE.ui.buttons["fullscreen"].onclick();
        }
      });
      editor.addListener("wordcount", function (type) {
        setCount(this, me);
      });

      function setCount(editor, ui) {
        editor.setOpt({
          wordCount: true,
          maximumWords: 10000,
          wordCountMsg:
            editor.options.wordCountMsg || editor.getLang("wordCountMsg"),
          wordOverFlowMsg:
            editor.options.wordOverFlowMsg || editor.getLang("wordOverFlowMsg")
        });
        var opt = editor.options,
          max = opt.maximumWords,
          msg = opt.wordCountMsg,
          errMsg = opt.wordOverFlowMsg,
          countDom = ui.getDom("wordcount");
        if (!opt.wordCount) {
          return;
        }
        var count = editor.getContentLength(true);
        if (count > max) {
          countDom.innerHTML = errMsg;
          editor.fireEvent("wordcountoverflow");
        } else {
          countDom.innerHTML = msg
            .replace("{#leave}", max - count)
            .replace("{#count}", count);
        }
      }

      editor.addListener("selectionchange", function () {
        if (editor.options.elementPathEnabled) {
          me[
          (editor.queryCommandState("elementpath") == -1 ? "dis" : "en") +
          "ableElementPath"
            ]();
        }
        if (editor.options.scaleEnabled) {
          me[
          (editor.queryCommandState("scale") == -1 ? "dis" : "en") +
          "ableScale"
            ]();
        }
      });
      var popup = new baidu.editor.ui.Popup({
        editor: editor,
        content: "",
        className: "edui-bubble",
        _onEditButtonClick: function () {
          this.hide();
          editor.ui._dialogs.linkDialog.open();
        },
        _onImgEditButtonClick: function (name) {
          this.hide();
          editor.ui._dialogs[name] && editor.ui._dialogs[name].open();
        },
        _onImgSetFloat: function (value) {
          this.hide();
          editor.execCommand("imagefloat", value);
        },
        _setIframeAlign: function (value) {
          var frame = popup.anchorEl;
          var newFrame = frame.cloneNode(true);
          switch (value) {
            case -2:
              newFrame.setAttribute("align", "");
              break;
            case -1:
              newFrame.setAttribute("align", "left");
              break;
            case 1:
              newFrame.setAttribute("align", "right");
              break;
          }
          frame.parentNode.insertBefore(newFrame, frame);
          domUtils.remove(frame);
          popup.anchorEl = newFrame;
          popup.showAnchor(popup.anchorEl);
        },
        _updateIframe: function () {
          var frame = (editor._iframe = popup.anchorEl);
          if (domUtils.hasClass(frame, "ueditor_baidumap")) {
            editor.selection.getRange().selectNode(frame).select();
            editor.ui._dialogs.mapDialog.open();
            popup.hide();
          } else {
            editor.ui._dialogs.insertframeDialog.open();
            popup.hide();
          }
        },
        _onRemoveButtonClick: function (cmdName) {
          editor.execCommand(cmdName);
          this.hide();
        },
        queryAutoHide: function (el) {
          if (el && el.ownerDocument == editor.document) {
            if (
              el.tagName.toLowerCase() == "img" ||
              domUtils.findParentByTagName(el, "a", true)
            ) {
              return el !== popup.anchorEl;
            }
          }
          return baidu.editor.ui.Popup.prototype.queryAutoHide.call(this, el);
        }
      });
      popup.render();
      if (editor.options.imagePopup) {
        editor.addListener("mouseover", function (t, evt) {
          evt = evt || window.event;
          var el = evt.target || evt.srcElement;
          if (
            editor.ui._dialogs.insertframeDialog &&
            /iframe/gi.test(el.tagName)
          ) {
            var html = popup.formatHtml(
              "<nobr>" +
              '<span onclick=$$._setIframeAlign(-2) class="edui-clickable">' +
              editor.getLang("default") +
              '</span>&nbsp;&nbsp;<span onclick=$$._setIframeAlign(-1) class="edui-clickable">' +
              editor.getLang("justifyleft") +
              '</span>&nbsp;&nbsp;<span onclick=$$._setIframeAlign(1) class="edui-clickable">' +
              editor.getLang("justifyright") +
              "</span>&nbsp;&nbsp;" +
              ' <span onclick="$$._updateIframe( this);" class="edui-clickable">' +
              editor.getLang("modify") +
              "</span></nobr>"
            );
            if (html) {
              popup.getDom("content").innerHTML = html;
              popup.anchorEl = el;
              popup.showAnchor(popup.anchorEl);
            } else {
              popup.hide();
            }
          }
        });
        editor.addListener("selectionchange", function (t, causeByUi) {
          if (!causeByUi) return;
          var html = "",
            str = "",
            img = editor.selection.getRange().getClosedNode(),
            dialogs = editor.ui._dialogs;
          if (img && img.tagName == "IMG") {
            var dialogName = "insertimageDialog";
            if (
              img.className.indexOf("edui-faked-video") != -1 ||
              img.className.indexOf("edui-upload-video") != -1
            ) {
              dialogName = "insertvideoDialog";
            }
            if (img.getAttribute("anchorname")) {
              dialogName = "anchorDialog";
              html = popup.formatHtml(
                "<nobr>" +
                '<span onclick=$$._onImgEditButtonClick("anchorDialog") class="edui-clickable">' +
                editor.getLang("modify") +
                "</span>&nbsp;&nbsp;" +
                "<span onclick=$$._onRemoveButtonClick('anchor') class=\"edui-clickable\">" +
                editor.getLang("delete") +
                "</span></nobr>"
              );
            }
            // if (img.getAttribute("data-word-image")) {
            //   //todo 放到dialog去做查询
            //   editor['data-word-image'] = [img.getAttribute("data-word-image")];
            //   dialogName = "wordimageDialog";
            // }
            if (
              domUtils.hasClass(img, "loadingclass") ||
              domUtils.hasClass(img, "loaderrorclass")
            ) {
              dialogName = "";
            }
            if (!dialogs[dialogName]) {
              return;
            }

            var actions = [];
            actions.push('<nobr />');
            actions.push('<span onclick=$$._onImgSetFloat("none") class="edui-clickable edui-popup-action-item">' +
              editor.getLang("default") +
              "</span>");
            actions.push('<span onclick=$$._onImgSetFloat("left") class="edui-clickable edui-popup-action-item">' +
              editor.getLang("justifyleft") +
              "</span>");
            actions.push('<span onclick=$$._onImgSetFloat("right") class="edui-clickable edui-popup-action-item">' +
              editor.getLang("justifyright") +
              "</span>");
            actions.push('<span onclick=$$._onImgSetFloat("center") class="edui-clickable edui-popup-action-item">' +
              editor.getLang("justifycenter") +
              "</span>");
            if (img.getAttribute('data-formula-image') !== null) {
              actions.push("<span onclick=\"$$._onImgEditButtonClick('formulaDialog');\" class='edui-clickable edui-popup-action-item'>" +
                 editor.getLang("formulaedit") + "</span>");
            }
            if (img.getAttribute("data-word-image")) {
              actions.push("<span onclick=\"$$._onImgEditButtonClick('wordimageDialog');\" class='edui-clickable edui-popup-action-item'>" +
                editor.getLang("save") +
                "</span>");
            } else {
              actions.push("<span onclick=\"$$._onImgEditButtonClick('" + dialogName + '\');" class="edui-clickable edui-popup-action-item">' +
                editor.getLang("modify") +
                "</span>");
            }
            actions.push("</nobr>");

            !html && (html = popup.formatHtml(actions.join("")));
          }
          if (editor.ui._dialogs.linkDialog) {
            var link = editor.queryCommandValue("link");
            var url;
            if (
              link &&
              (url = link.getAttribute("_href") || link.getAttribute("href", 2))
            ) {
              var txt = url;
              if (url.length > 30) {
                txt = url.substring(0, 20) + "...";
              }
              if (html) {
                html += '<div style="height:5px;"></div>';
              }
              html += popup.formatHtml(
                "<nobr>" +
                editor.getLang("anchorMsg") +
                ': <a target="_blank" href="' +
                url +
                '" title="' +
                url +
                '" >' +
                txt +
                "</a>" +
                ' <span class="edui-clickable" onclick="$$._onEditButtonClick();">' +
                editor.getLang("modify") +
                "</span>" +
                ' <span class="edui-clickable" onclick="$$._onRemoveButtonClick(\'unlink\');"> ' +
                editor.getLang("clear") +
                "</span></nobr>"
              );
              popup.showAnchor(link);
            }
          }

          if (html) {
            popup.getDom("content").innerHTML = html;
            popup.anchorEl = img || link;
            popup.showAnchor(popup.anchorEl);
          } else {
            popup.hide();
          }
        });
      }
    },
    _initToolbars: function () {
      var editor = this.editor;
      var toolbars = this.toolbars || [];
      if(toolbars[0]){
        toolbars[0].unshift(
          'message'
        );
      }
      var toolbarUis = [];
      var extraUIs = [];
      for (var i = 0; i < toolbars.length; i++) {
        var toolbar = toolbars[i];
        var toolbarUi = new baidu.editor.ui.Toolbar({
          theme: editor.options.theme
        });
        for (var j = 0; j < toolbar.length; j++) {
          var toolbarItem = toolbar[j];
          var toolbarItemUi = null;
          if (typeof toolbarItem == "string") {
            toolbarItem = toolbarItem.toLowerCase();
            if (toolbarItem === "|") {
              toolbarItem = "Separator";
            }
            if (toolbarItem === "||") {
              toolbarItem = "Breakline";
            }
            var ui = baidu.editor.ui[toolbarItem];
            if (ui) {
              if (utils.isFunction(ui)) {
                toolbarItemUi = new baidu.editor.ui[toolbarItem](editor);
              } else {
                if (ui.id && ui.id !== editor.key) {
                  continue;
                }
                var itemUI = ui.execFn.call(editor, editor, toolbarItem);
                if (itemUI) {
                  if (ui.index === undefined) {
                    toolbarUi.add(itemUI);
                    continue;
                  } else {
                    extraUIs.push({
                      index: ui.index,
                      itemUI: itemUI
                    });
                  }
                }
              }
            }
            //fullscreen这里单独处理一下，放到首行去
            if (toolbarItem === "fullscreen") {
              if (toolbarUis && toolbarUis[0]) {
                toolbarUis[0].items.splice(0, 0, toolbarItemUi);
              } else {
                toolbarItemUi && toolbarUi.items.splice(0, 0, toolbarItemUi);
              }
              continue;
            }
          } else {
            toolbarItemUi = toolbarItem;
          }
          if (toolbarItemUi && toolbarItemUi.id) {
            toolbarUi.add(toolbarItemUi);
          }
        }
        toolbarUis[i] = toolbarUi;
      }

      //接受外部定制的UI

      utils.each(extraUIs, function (obj) {
        toolbarUi.add(obj.itemUI, obj.index);
      });
      this.toolbars = toolbarUis;
    },
    getHtmlTpl: function () {
      return (
        '<div id="##" class="%%">' +
        '<div id="##_toolbarbox" class="%%-toolbarbox">' +
        (this.toolbars.length
          ? '<div id="##_toolbarboxouter" class="%%-toolbarboxouter"><div class="%%-toolbarboxinner">' +
          this.renderToolbarBoxHtml() +
          "</div></div>"
          : "") +
        '<div id="##_toolbarmsg" class="%%-toolbarmsg" style="display:none;">' +
        '<div id = "##_upload_dialog" class="%%-toolbarmsg-upload" onclick="$$.showWordImageDialog();">' +
        this.editor.getLang("clickToUpload") +
        "</div>" +
        '<div class="%%-toolbarmsg-close" onclick="$$.hideToolbarMsg();">x</div>' +
        '<div id="##_toolbarmsg_label" class="%%-toolbarmsg-label"></div>' +
        '<div style="height:0;overflow:hidden;clear:both;"></div>' +
        "</div>" +
        '<div id="##_message_holder" class="%%-messageholder"></div>' +
        "</div>" +
        '<div id="##_iframeholder" class="%%-iframeholder">' +
        "</div>" +
        //modify wdcount by matao
        '<div id="##_bottombar" class="%%-bottomContainer"><table><tr>' +
        '<td id="##_elementpath" class="%%-bottombar"></td>' +
        '<td id="##_wordcount" class="%%-wordcount"></td>' +
        '<td id="##_scale" class="%%-scale"><div class="%%-icon"></div></td>' +
        "</tr></table></div>" +
        '<div id="##_scalelayer"></div>' +
        "</div>"
      );
    },
    showWordImageDialog: function () {
      this._dialogs["wordimageDialog"].open();
    },
    renderToolbarBoxHtml: function () {
      var buff = [];
      for (var i = 0; i < this.toolbars.length; i++) {
        buff.push(this.toolbars[i].renderHtml());
      }
      return buff.join("");
    },
    setFullScreen: function (fullscreen) {
      var editor = this.editor,
        container = editor.container.parentNode.parentNode;
      if (this._fullscreen != fullscreen) {
        this._fullscreen = fullscreen;
        this.editor.fireEvent("beforefullscreenchange", fullscreen);
        if (baidu.editor.browser.gecko) {
          var bk = editor.selection.getRange().createBookmark();
        }
        if (fullscreen) {
          while (container.tagName !== "BODY") {
            var position = baidu.editor.dom.domUtils.getComputedStyle(
              container,
              "position"
            );
            nodeStack.push(position);
            container.style.position = "static";
            container = container.parentNode;
          }
          this._bakHtmlOverflow = document.documentElement.style.overflow;
          this._bakBodyOverflow = document.body.style.overflow;
          this._bakAutoHeight = this.editor.autoHeightEnabled;
          this._bakScrollTop = Math.max(
            document.documentElement.scrollTop,
            document.body.scrollTop
          );

          this._bakEditorContaninerWidth = editor.iframe.parentNode.offsetWidth;
          if (this._bakAutoHeight) {
            //当全屏时不能执行自动长高
            editor.autoHeightEnabled = false;
            this.editor.disableAutoHeight();
          }

          document.documentElement.style.overflow = "hidden";
          //修复，滚动条不收起的问题

          window.scrollTo(0, 0);
          this._bakCssText = this.getDom().style.cssText;
          this._bakCssText1 = this.getDom("iframeholder").style.cssText;
          editor.iframe.parentNode.style.width = "";
          this._updateFullScreen();
        } else {
          while (container.tagName !== "BODY") {
            container.style.position = nodeStack.shift();
            container = container.parentNode;
          }
          this.getDom().style.cssText = this._bakCssText;
          this.getDom("iframeholder").style.cssText = this._bakCssText1;
          if (this._bakAutoHeight) {
            editor.autoHeightEnabled = true;
            this.editor.enableAutoHeight();
          }

          document.documentElement.style.overflow = this._bakHtmlOverflow;
          document.body.style.overflow = this._bakBodyOverflow;
          editor.iframe.parentNode.style.width =
            this._bakEditorContaninerWidth + "px";
          window.scrollTo(0, this._bakScrollTop);
        }
        if (browser.gecko && editor.body.contentEditable === "true") {
          var input = document.createElement("input");
          document.body.appendChild(input);
          editor.body.contentEditable = false;
          setTimeout(function () {
            input.focus();
            setTimeout(function () {
              editor.body.contentEditable = true;
              editor.fireEvent("fullscreenchanged", fullscreen);
              editor.selection.getRange().moveToBookmark(bk).select(true);
              baidu.editor.dom.domUtils.remove(input);
              fullscreen && window.scroll(0, 0);
            }, 0);
          }, 0);
        }

        if (editor.body.contentEditable === "true") {
          this.editor.fireEvent("fullscreenchanged", fullscreen);
          this.triggerLayout();
        }
      }
    },
    _updateFullScreen: function () {
      if (this._fullscreen) {
        var vpRect = uiUtils.getViewportRect();
        this.getDom().style.cssText =
          "border:0;position:absolute;left:0;top:var(--ueditor-top-offset," +
          (this.editor.options.topOffset || 0) +
          "px);width:" +
          vpRect.width +
          "px;height:" +
          vpRect.height +
          "px;z-index:" +
          (this.getDom().style.zIndex * 1 + 100);
        uiUtils.setViewportOffset(this.getDom(), {
          left: 0,
          // top: this.editor.options.topOffset || 0
        });
        this.editor.setHeight(
          vpRect.height -
          this.getDom("toolbarbox").offsetHeight -
          this.getDom("bottombar").offsetHeight -
          (this.editor.options.topOffset || 0),
          true
        );
        //不手动调一下，会导致全屏失效
        if (browser.gecko) {
          try {
            window.onresize();
          } catch (e) {
          }
        }
      }
    },
    _updateElementPath: function () {
      var bottom = this.getDom("elementpath"),
        list;
      if (
        this.elementPathEnabled &&
        (list = this.editor.queryCommandValue("elementpath"))
      ) {
        var buff = [];
        for (var i = 0, ci; (ci = list[i]); i++) {
          buff[i] = this.formatHtml(
            '<span unselectable="on" onclick="$$.editor.execCommand(&quot;elementpath&quot;, &quot;' +
            i +
            '&quot;);">' +
            ci +
            "</span>"
          );
        }
        bottom.innerHTML =
          '<div class="edui-editor-breadcrumb" onmousedown="return false;">' +
          this.editor.getLang("elementPathTip") +
          ": " +
          buff.join(" &gt; ") +
          "</div>";
      } else {
        bottom.style.display = "none";
      }
    },
    disableElementPath: function () {
      var bottom = this.getDom("elementpath");
      bottom.innerHTML = "";
      bottom.style.display = "none";
      this.elementPathEnabled = false;
    },
    enableElementPath: function () {
      var bottom = this.getDom("elementpath");
      bottom.style.display = "";
      this.elementPathEnabled = true;
      this._updateElementPath();
    },
    _scale: function () {
      var doc = document,
        editor = this.editor,
        editorHolder = editor.container,
        editorDocument = editor.document,
        toolbarBox = this.getDom("toolbarbox"),
        bottombar = this.getDom("bottombar"),
        scale = this.getDom("scale"),
        scalelayer = this.getDom("scalelayer");

      var isMouseMove = false,
        position = null,
        minEditorHeight = 0,
        minEditorWidth = editor.options.minFrameWidth,
        pageX = 0,
        pageY = 0,
        scaleWidth = 0,
        scaleHeight = 0;

      function down() {
        position = domUtils.getXY(editorHolder);

        if (!minEditorHeight) {
          minEditorHeight =
            editor.options.minFrameHeight +
            toolbarBox.offsetHeight +
            bottombar.offsetHeight;
        }

        scalelayer.style.cssText =
          "position:absolute;left:0;display:;top:0;background-color:#41ABFF;opacity:0.4;filter: Alpha(opacity=40);width:" +
          editorHolder.offsetWidth +
          "px;height:" +
          editorHolder.offsetHeight +
          "px;z-index:" +
          (editor.options.zIndex + 1);

        domUtils.on(doc, "mousemove", move);
        domUtils.on(editorDocument, "mouseup", up);
        domUtils.on(doc, "mouseup", up);
      }

      var me = this;
      //by xuheng 全屏时关掉缩放
      this.editor.addListener("fullscreenchanged", function (e, fullScreen) {
        if (fullScreen) {
          me.disableScale();
        } else {
          if (me.editor.options.scaleEnabled) {
            me.enableScale();
            var tmpNode = me.editor.document.createElement("span");
            me.editor.body.appendChild(tmpNode);
            me.editor.body.style.height =
              Math.max(
                domUtils.getXY(tmpNode).y,
                me.editor.iframe.offsetHeight - 20
              ) + "px";
            domUtils.remove(tmpNode);
          }
        }
      });

      function move(event) {
        clearSelection();
        var e = event || window.event;
        pageX = e.pageX || doc.documentElement.scrollLeft + e.clientX;
        pageY = e.pageY || doc.documentElement.scrollTop + e.clientY;
        scaleWidth = pageX - position.x;
        scaleHeight = pageY - position.y;

        if (scaleWidth >= minEditorWidth) {
          isMouseMove = true;
          scalelayer.style.width = scaleWidth + "px";
        }
        if (scaleHeight >= minEditorHeight) {
          isMouseMove = true;
          scalelayer.style.height = scaleHeight + "px";
        }
      }

      function up() {
        if (isMouseMove) {
          isMouseMove = false;
          editor.ui._actualFrameWidth = scalelayer.offsetWidth - 2;
          editorHolder.style.width = editor.ui._actualFrameWidth + "px";

          editor.setHeight(
            scalelayer.offsetHeight -
            bottombar.offsetHeight -
            toolbarBox.offsetHeight -
            2,
            true
          );
        }
        if (scalelayer) {
          scalelayer.style.display = "none";
        }
        clearSelection();
        domUtils.un(doc, "mousemove", move);
        domUtils.un(editorDocument, "mouseup", up);
        domUtils.un(doc, "mouseup", up);
      }

      function clearSelection() {
        if (browser.ie) doc.selection.clear();
        else window.getSelection().removeAllRanges();
      }

      this.enableScale = function () {
        //trace:2868
        if (editor.queryCommandState("source") == 1) return;
        scale.style.display = "";
        this.scaleEnabled = true;
        domUtils.on(scale, "mousedown", down);
      };
      this.disableScale = function () {
        scale.style.display = "none";
        this.scaleEnabled = false;
        domUtils.un(scale, "mousedown", down);
      };
    },
    isFullScreen: function () {
      return this._fullscreen;
    },
    postRender: function () {
      UIBase.prototype.postRender.call(this);
      for (var i = 0; i < this.toolbars.length; i++) {
        this.toolbars[i].postRender();
      }
      var me = this;
      var timerId,
        domUtils = baidu.editor.dom.domUtils,
        updateFullScreenTime = function () {
          clearTimeout(timerId);
          timerId = setTimeout(function () {
            me._updateFullScreen();
          });
        };
      domUtils.on(window, "resize", updateFullScreenTime);

      me.addListener("destroy", function () {
        domUtils.un(window, "resize", updateFullScreenTime);
        clearTimeout(timerId);
      });
    },
    showToolbarMsg: function (msg, flag) {
      this.getDom("toolbarmsg_label").innerHTML = msg;
      this.getDom("toolbarmsg").style.display = "";
      //
      if (!flag) {
        var w = this.getDom("upload_dialog");
        w.style.display = "none";
      }
    },
    hideToolbarMsg: function () {
      this.getDom("toolbarmsg").style.display = "none";
    },
    mapUrl: function (url) {
      return url
        ? url.replace("~/", this.editor.options.UEDITOR_CORS_URL || "")
        : "";
    },
    triggerLayout: function () {
      var dom = this.getDom();
      if (dom.style.zoom == "1") {
        dom.style.zoom = "100%";
      } else {
        dom.style.zoom = "1";
      }
    }
  };
  utils.inherits(EditorUI, baidu.editor.ui.UIBase);

  var instances = {};

  UE.ui.Editor = function (options) {
    var editor = new UE.Editor(options);
    editor.options.editor = editor;
    utils.loadFile(document, {
      href:
        editor.options.themePath + editor.options.theme + "/css/ueditor.css?20230319",
      tag: "link",
      type: "text/css",
      rel: "stylesheet"
    });

    var oldRender = editor.render;
    editor.render = function (holder) {
      if (holder.constructor === String) {
        editor.key = holder;
        instances[holder] = editor;
      }
      utils.domReady(function () {
        editor.langIsReady
          ? renderUI()
          : editor.addListener("langReady", renderUI);

        function renderUI() {
          editor.setOpt({
            labelMap: editor.options.labelMap || editor.getLang("labelMap")
          });
          new EditorUI(editor.options);
          if (holder) {
            if (holder.constructor === String) {
              holder = document.getElementById(holder);
            }
            holder &&
            holder.getAttribute("name") &&
            (editor.options.textarea = holder.getAttribute("name"));
            if (holder && /script|textarea/gi.test(holder.tagName)) {
              var newDiv = document.createElement("div");
              holder.parentNode.insertBefore(newDiv, holder);
              var cont = holder.value || holder.innerHTML;
              editor.options.initialContent = /^[\t\r\n ]*$/.test(cont)
                ? editor.options.initialContent
                : cont
                  .replace(/>[\n\r\t]+([ ]{4})+/g, ">")
                  .replace(/[\n\r\t]+([ ]{4})+</g, "<")
                  .replace(/>[\n\r\t]+</g, "><");
              holder.className && (newDiv.className = holder.className);
              holder.style.cssText &&
              (newDiv.style.cssText = holder.style.cssText);
              if (/textarea/i.test(holder.tagName)) {
                editor.textarea = holder;
                editor.textarea.style.display = "none";
              } else {
                holder.parentNode.removeChild(holder);
              }
              if (holder.id) {
                newDiv.id = holder.id;
                domUtils.removeAttributes(holder, "id");
              }
              holder = newDiv;
              holder.innerHTML = "";
            }
          }
          domUtils.addClass(holder, "edui-" + editor.options.theme);
          editor.ui.render(holder);
          var opt = editor.options;
          //给实例添加一个编辑器的容器引用
          editor.container = editor.ui.getDom();
          var parents = domUtils.findParents(holder, true);
          var displays = [];
          for (var i = 0, ci; (ci = parents[i]); i++) {
            displays[i] = ci.style.display;
            ci.style.display = "block";
          }
          if (opt.initialFrameWidth) {
            opt.minFrameWidth = opt.initialFrameWidth;
          } else {
            opt.minFrameWidth = opt.initialFrameWidth = holder.offsetWidth;
            var styleWidth = holder.style.width;
            if (/%$/.test(styleWidth)) {
              opt.initialFrameWidth = styleWidth;
            }
          }
          if (opt.initialFrameHeight) {
            opt.minFrameHeight = opt.initialFrameHeight;
          } else {
            opt.initialFrameHeight = opt.minFrameHeight = holder.offsetHeight;
          }
          for (var i = 0, ci; (ci = parents[i]); i++) {
            ci.style.display = displays[i];
          }
          //编辑器最外容器设置了高度，会导致，编辑器不占位
          //todo 先去掉，没有找到原因
          if (holder.style.height) {
            holder.style.height = "";
          }
          editor.container.style.width =
            opt.initialFrameWidth +
            (/%$/.test(opt.initialFrameWidth) ? "" : "px");
          editor.container.style.zIndex = opt.zIndex;
          oldRender.call(editor, editor.ui.getDom("iframeholder"));
          editor.fireEvent("afteruiready");
        }
      });
    };
    return editor;
  };

  /**
   * @file
   * @name UE
   * @short UE
   * @desc UEditor的顶部命名空间
   */
  /**
   * @name getEditor
   * @since 1.2.4+
   * @grammar UE.getEditor(id,[opt])  =>  Editor实例
   * @desc 提供一个全局的方法得到编辑器实例
   *
   * * ''id''  放置编辑器的容器id, 如果容器下的编辑器已经存在，就直接返回
   * * ''opt'' 编辑器的可选参数
   * @example
   *  UE.getEditor('containerId',{onready:function(){//创建一个编辑器实例
   *      this.setContent('hello')
   *  }});
   *  UE.getEditor('containerId'); //返回刚创建的实例
   *
   */
  UE.getEditor = function (id, opt) {
    var editor = instances[id];
    if (!editor) {
      editor = instances[id] = new UE.ui.Editor(opt);
      editor.render(id);
    }
    return editor;
  };

  UE.delEditor = function (id) {
    var editor;
    if ((editor = instances[id])) {
      editor.key && editor.destroy();
      delete instances[id];
    }
  };

  UE.registerUI = function (uiName, fn, index, editorId) {
    utils.each(uiName.split(/\s+/), function (name) {
      baidu.editor.ui[name] = {
        id: editorId,
        execFn: fn,
        index: index
      };
    });
  };
})();


// adapter/message.js
UE.registerUI("message", function(editor) {
  var editorui = baidu.editor.ui;
  var Message = editorui.Message;
  var holder;
  var _messageItems = [];
  var me = editor;

  me.setOpt("enableMessageShow", true);
  if (me.getOpt("enableMessageShow") === false) {
    return;
  }

  me.addListener("ready", function() {
    holder = document.getElementById(me.ui.id + "_message_holder");
    updateHolderPos();
    setTimeout(function() {
      updateHolderPos();
    }, 500);
  });

  me.addListener("showmessage", function(type, opt) {
    opt = utils.isString(opt)
      ? {
          content: opt
        }
      : opt;
    var message = new Message({
      timeout: opt.timeout,
      type: opt.type,
      content: opt.content,
      keepshow: opt.keepshow,
      editor: me
    }),
      mid = opt.id || "msg_" + (+new Date()).toString(36);
    message.render(holder);
    _messageItems[mid] = message;
    message.reset(opt);
    updateHolderPos();
    return mid;
  });

  me.addListener("updatemessage", function(type, id, opt) {
    opt = utils.isString(opt)
      ? {
          content: opt
        }
      : opt;
    var message = _messageItems[id];
    message.render(holder);
    message && message.reset(opt);
  });

  me.addListener("hidemessage", function(type, id) {
    var message = _messageItems[id];
    message && message.hide();
  });

  function updateHolderPos() {
    if (!holder || !me.ui) return;
    var toolbarbox = me.ui.getDom("toolbarbox");
    if (toolbarbox) {
      holder.style.top = toolbarbox.offsetHeight + 3 + "px";
    }
    holder.style.zIndex =
      Math.max(me.options.zIndex, me.iframe.style.zIndex) + 1;
  }
});



})();
